diff --git a/.gitignore b/.gitignore index fab584f09e..59d279542d 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ lost+found nohup.out nohup.out.old ATTIC +# IntelliJ auto-generated files +.idea diff --git a/.scalafmt.conf b/.scalafmt.conf index cd36a01e4d..055fff1659 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -4,3 +4,4 @@ maxColumn = 110 spaces.inImportCurlyBraces = true rewrite.rules = [SortImports, RedundantParens, SortModifiers] rewrite.redundantBraces.stringInterpolation = true +runner.dialect = scala213 diff --git a/app/Env.scala b/app/Env.scala index 5aef82d4a8..03cfff51e2 100644 --- a/app/Env.scala +++ b/app/Env.scala @@ -117,16 +117,21 @@ final class Env( text = "Team IDs that always get their tournaments visible on /tournament. Separated by commas.".some ) lazy val prizeTournamentMakers = memo.settingStore[UserIds]( - "prizeTournamentMakers ", + "prizeTournamentMakers", default = UserIds(Nil), text = "User IDs who can make prize tournaments (arena & swiss) without a warning. Separated by commas.".some ) lazy val apiExplorerGamesPerSecond = memo.settingStore[Int]( - "apiExplorerGamesPerSecond ", - default = 200, + "apiExplorerGamesPerSecond", + default = 300, text = "Opening explorer games per second".some ) + lazy val pieceImageExternal = memo.settingStore[Boolean]( + "pieceImageExternal", + default = false, + text = "Use external piece images".some + ) lazy val preloader = wire[mashup.Preload] lazy val socialInfo = wire[mashup.UserInfo.SocialApi] diff --git a/app/controllers/Api.scala b/app/controllers/Api.scala index 49b1e96f7b..50009ca937 100644 --- a/app/controllers/Api.scala +++ b/app/controllers/Api.scala @@ -10,6 +10,7 @@ import lila.api.{ Context, GameApiV2 } import lila.app._ import lila.common.config.{ MaxPerPage, MaxPerSecond } import lila.common.{ HTTPRequest, IpAddress } +import lila.common.LightUser final class Api( env: Env, @@ -74,17 +75,20 @@ final class Api( def usersStatus = ApiRequest { req => val ids = get("ids", req).??(_.split(',').take(100).toList map lila.user.User.normalize) - env.user.lightUserApi asyncMany ids dmap (_.flatten) map { users => + env.user.lightUserApi asyncMany ids dmap (_.flatten) flatMap { users => val streamingIds = env.streamer.liveStreamApi.userIds - toApiResult { - users.map { u => - lila.common.LightUser.lightUserWrites - .writes(u) - .add("online" -> env.socket.isOnline(u.id)) - .add("playing" -> env.round.playing(u.id)) - .add("streaming" -> streamingIds(u.id)) + def toJson(u: LightUser) = + lila.common.LightUser.lightUserWrites + .writes(u) + .add("online" -> env.socket.isOnline(u.id)) + .add("playing" -> env.round.playing(u.id)) + .add("streaming" -> streamingIds(u.id)) + if (getBool("withGameIds", req)) users.map { u => + (env.round.playing(u.id) ?? env.game.cached.lastPlayedPlayingId(u.id)) map { gameId => + toJson(u).add("playingId", gameId) } - } + }.sequenceFu map toApiResult + else fuccess(toApiResult(users map toJson)) } } diff --git a/app/controllers/Dev.scala b/app/controllers/Dev.scala index ad91309cdb..fff8b67fe9 100644 --- a/app/controllers/Dev.scala +++ b/app/controllers/Dev.scala @@ -26,7 +26,9 @@ final class Dev(env: Env) extends LilaController(env) { env.fishnet.openingBookDepth, env.noDelaySecretSetting, env.featuredTeamsSetting, - env.prizeTournamentMakers + env.prizeTournamentMakers, + env.pieceImageExternal, + env.evalCache.enable ) def settings = diff --git a/app/controllers/Irwin.scala b/app/controllers/Irwin.scala index 1cdf029257..74851ceb76 100644 --- a/app/controllers/Irwin.scala +++ b/app/controllers/Irwin.scala @@ -7,7 +7,7 @@ final class Irwin(env: Env) extends LilaController(env) { import lila.irwin.JSONHandlers.reportReader def dashboard = - Secure(_.SeeReport) { implicit ctx => _ => + Secure(_.MarkEngine) { implicit ctx => _ => env.irwin.api.dashboard map { d => Ok(views.html.irwin.dashboard(d)) } diff --git a/app/controllers/LilaController.scala b/app/controllers/LilaController.scala index c2d3f5a409..d0a2f8d1b5 100644 --- a/app/controllers/LilaController.scala +++ b/app/controllers/LilaController.scala @@ -65,10 +65,11 @@ abstract private[controllers] class LilaController(val env: Env) implicit def reqConfig(implicit req: RequestHeader) = ui.EmbedConfig(req) def reqLang(implicit req: RequestHeader) = I18nLangPicker(req) - protected def EnableSharedArrayBuffer(res: Result): Result = + protected def EnableSharedArrayBuffer(res: Result)(implicit req: RequestHeader): Result = res.withHeaders( - "Cross-Origin-Opener-Policy" -> "same-origin", - "Cross-Origin-Embedder-Policy" -> "require-corp" + "Cross-Origin-Opener-Policy" -> "same-origin", + "Cross-Origin-Embedder-Policy" -> (if (HTTPRequest isChrome96OrMore req) "credentialless" + else "require-corp") ) protected def NoCache(res: Result): Result = diff --git a/app/controllers/Mod.scala b/app/controllers/Mod.scala index 171a7adbe8..ad60b5556d 100644 --- a/app/controllers/Mod.scala +++ b/app/controllers/Mod.scala @@ -108,6 +108,7 @@ final class Mod( suspect <- modApi.setTroll(me, prev, prev.user.marks.troll) _ <- env.msg.api.systemPost(suspect.user.id, preset.text) _ <- env.mod.logApi.modMessage(me.id, suspect.user.id, preset.name) + _ <- preset.isNameClose ?? env.irc.api.nameClosePreset(username) } yield (inquiry, suspect).some } } diff --git a/app/controllers/PlayApi.scala b/app/controllers/PlayApi.scala index 7caf2360ba..d4429af035 100644 --- a/app/controllers/PlayApi.scala +++ b/app/controllers/PlayApi.scala @@ -122,15 +122,22 @@ final class PlayApi( def boardCommandGet(cmd: String) = ScopedBody(_.Board.Play) { implicit req => me => cmd.split('/') match { - case Array("game", id, "chat") => - WithPovAsBoard(id, me) { pov => - env.chat.api.userChat.find(lila.chat.Chat.Id(pov.game.id)) map - lila.chat.JsonView.boardApi map JsonOk - } - case _ => notFoundJson("No such command") + case Array("game", id, "chat") => WithPovAsBoard(id, me)(getChat) + case _ => notFoundJson("No such command") } } + def botCommandGet(cmd: String) = + ScopedBody(_.Bot.Play) { implicit req => me => + cmd.split('/') match { + case Array("game", id, "chat") => WithPovAsBot(id, me)(getChat) + case _ => notFoundJson("No such command") + } + } + + private def getChat(pov: Pov) = + env.chat.api.userChat.find(lila.chat.Chat.Id(pov.game.id)) map lila.chat.JsonView.boardApi map JsonOk + // utils private def toResult(f: Funit): Fu[Result] = catchClientError(f inject jsonOkResult) diff --git a/app/controllers/Round.scala b/app/controllers/Round.scala index 4c660e1085..5102d54314 100644 --- a/app/controllers/Round.scala +++ b/app/controllers/Round.scala @@ -131,7 +131,7 @@ final class Round( Open { implicit ctx => proxyPov(gameId, color) flatMap { case Some(pov) => - get("pov") match { + get("pov").map(UserModel.normalize) match { case Some(requestedPov) => (pov.player.userId, pov.opponent.userId) match { case (Some(_), Some(opponent)) if opponent == requestedPov => diff --git a/app/controllers/Study.scala b/app/controllers/Study.scala index 4907fff8d9..4c496a9127 100644 --- a/app/controllers/Study.scala +++ b/app/controllers/Study.scala @@ -328,15 +328,17 @@ final class Study( env.study.api.importPgns( StudyModel.Id(id), data.toChapterDatas, - sticky = data.sticky + sticky = data.sticky, + ctx.pref.showRatings )(Who(me.id, lila.socket.Socket.Sri(sri))) ) } } def admin(id: String) = - Secure(_.StudyAdmin) { _ => me => - env.study.api.adminInvite(id, me) inject Redirect(routes.Study.show(id)) + Secure(_.StudyAdmin) { ctx => me => + env.study.api.adminInvite(id, me) inject (if (HTTPRequest isXhr ctx.req) NoContent + else Redirect(routes.Study.show(id))) } def embed(id: String, chapterId: String) = diff --git a/app/templating/AssetHelper.scala b/app/templating/AssetHelper.scala index ec5b3637dc..1e97f83cb3 100644 --- a/app/templating/AssetHelper.scala +++ b/app/templating/AssetHelper.scala @@ -22,12 +22,11 @@ trait AssetHelper { self: I18nHelper with SecurityHelper => def assetVersion = AssetVersion.current - def assetUrl(path: String): String = s"$assetBaseUrl/assets/_$assetVersion/$path" + def assetUrl(path: String): String = s"$assetBaseUrl/assets/_$assetVersion/$path" + def staticAssetUrl(path: String): String = s"$assetBaseUrl/assets/$path" def cdnUrl(path: String) = s"$assetBaseUrl$path" - def dbImageUrl(path: String) = s"$assetBaseUrl/image/$path" - def cssTag(name: String)(implicit ctx: Context): Frag = cssTagWithTheme(name, ctx.currentBg) diff --git a/app/templating/Environment.scala b/app/templating/Environment.scala index 1dec94f4ca..1e9fdc4420 100644 --- a/app/templating/Environment.scala +++ b/app/templating/Environment.scala @@ -51,6 +51,6 @@ object Environment ) val spinner: Frag = raw( - """
""" + """
""" ) } diff --git a/app/ui/scalatags.scala b/app/ui/scalatags.scala index 73d7c3b85b..db018d3444 100644 --- a/app/ui/scalatags.scala +++ b/app/ui/scalatags.scala @@ -99,15 +99,16 @@ trait ScalatagsPrefix { // what to import in a pure scalatags template trait ScalatagsTemplate - extends Styles - with ScalatagsBundle + extends ScalatagsBundle with ScalatagsAttrs with ScalatagsExtensions with ScalatagsSnippets with ScalatagsPrefix { - val trans = lila.i18n.I18nKeys - def main = scalatags.Text.tags2.main + val trans = lila.i18n.I18nKeys + def main = scalatags.Text.tags2.main + def cssWidth = scalatags.Text.styles.width + def cssHeight = scalatags.Text.styles.height /* Convert play URLs to scalatags attributes with toString */ implicit val playCallAttr = genericAttr[play.api.mvc.Call] diff --git a/app/views/account/bits.scala b/app/views/account/bits.scala index ce40637e2e..c95f002205 100644 --- a/app/views/account/bits.scala +++ b/app/views/account/bits.scala @@ -17,7 +17,7 @@ object bits { div(cls := "personal-data__header")( p("Here is all personal information Lichess has about ", userLink(u)), a(cls := "button", href := s"${routes.Account.data}?user=${u.id}&text=1", downloadAttr)( - trans.downloadRaw() + trans.download() ) ) ) diff --git a/app/views/base/layout.scala b/app/views/base/layout.scala index f2f8e04818..5bb2fd710d 100644 --- a/app/views/base/layout.scala +++ b/app/views/base/layout.scala @@ -34,23 +34,38 @@ object layout { def pieceSprite(ps: lila.pref.PieceSet): Frag = link( id := "piece-sprite", - href := assetUrl(s"piece-css/$ps.css"), + href := assetUrl(s"piece-css/$ps.${env.pieceImageExternal.get() ?? "external."}css"), rel := "stylesheet" ) } import bits._ private val noTranslate = raw("""""") - private def fontPreload(implicit ctx: Context) = - raw { - s"""""" + - !ctx.pref.pieceNotationIsLetter ?? - s"""""" + + private def preload(href: String, as: String, crossorigin: Boolean, tpe: Option[String] = None) = + raw(s""" + s"""type="$t" """ + )}${crossorigin ?? "crossorigin"}>""") + + private def fontPreload(implicit ctx: Context) = frag( + preload(assetUrl(s"font/lichess.woff2"), "font", crossorigin = true, "font/woff2".some), + !ctx.pref.pieceNotationIsLetter option + preload(assetUrl(s"font/lichess.chess.woff2"), "font", crossorigin = true, "font/woff2".some) + ) + private def boardPreload(implicit ctx: Context) = frag( + preload(assetUrl(s"images/board/${ctx.currentTheme.file}"), "image", crossorigin = false), + ctx.pref.is3d option + preload(s"images/staunton/board/${ctx.currentTheme3d.file}", "image", crossorigin = false) + ) + private def piecesPreload(implicit ctx: Context) = + env.pieceImageExternal.get() option raw { + (for { + c <- List('w', 'b') + p <- List('K', 'Q', 'R', 'B', 'N', 'P') + href = staticAssetUrl(s"piece/${ctx.currentPieceSet.name}/$c$p.svg") + } yield s"""""").mkString } + private val manifests = raw( """""" ) @@ -149,7 +164,7 @@ object layout { def lichessJsObject(nonce: Nonce)(implicit lang: Lang) = embedJsUnsafe( - s"""lichess={load:new Promise(r=>{window.onload=r}),quantity:${lila.i18n + s"""lichess={load:new Promise(r=>{document.addEventListener("DOMContentLoaded",r)}),quantity:${lila.i18n .JsQuantity(lang)}};$timeagoLocaleScript""", nonce ) @@ -229,7 +244,7 @@ object layout { content := openGraph.fold(trans.siteDescription.txt())(o => o.description), name := "description" ), - link(rel := "mask-icon", href := assetUrl("logo/lichess.svg"), color := "black"), + link(rel := "mask-icon", href := assetUrl("logo/lichess.svg"), attr("color") := "black"), favicons, !robots option raw(""""""), noTranslate, @@ -248,6 +263,8 @@ object layout { ) }, fontPreload, + boardPreload, + piecesPreload, manifests, jsLicense ), diff --git a/app/views/base/notFound.scala b/app/views/base/notFound.scala index 459e53ba7c..0725f2900e 100644 --- a/app/views/base/notFound.scala +++ b/app/views/base/notFound.scala @@ -32,8 +32,8 @@ object notFound { iframe( src := assetUrl(s"vendor/ChessPursuit/bin-release/index.html"), st.frameborder := 0, - width := 400, - height := 500 + widthA := 400, + heightA := 500 ), p(cls := "credits")( a(href := "https://github.com/Saturnyn/ChessPursuit")("ChessPursuit"), diff --git a/app/views/coach/picture.scala b/app/views/coach/picture.scala index 083f2bec66..019b38ab77 100644 --- a/app/views/coach/picture.scala +++ b/app/views/coach/picture.scala @@ -46,8 +46,8 @@ object picture { img( widthA := Coach.imageSize, heightA := Coach.imageSize, - width := cssSize, - height := cssSize, + cssWidth := cssSize, + cssHeight := cssSize, cls := "picture", src := url(c.coach), alt := s"${c.user.titleUsername} Lichess coach picture" diff --git a/app/views/coach/show.scala b/app/views/coach/show.scala index 5f544a59fd..f1dc4f3b46 100644 --- a/app/views/coach/show.scala +++ b/app/views/coach/show.scala @@ -97,8 +97,8 @@ object show { div(cls := "list")( profile.youtubeUrls.map { url => iframe( - width := "256", - height := "192", + widthA := "256", + heightA := "192", src := url.value, attr("frameborder") := "0", frame.allowfullscreen diff --git a/app/views/mobile.scala b/app/views/mobile.scala index 039c6805d6..a340e42239 100644 --- a/app/views/mobile.scala +++ b/app/views/mobile.scala @@ -47,16 +47,16 @@ object mobile { ), div(cls := "right-side")( img( - cls := "nexus5-playing", - width := "268", - height := "513", - src := assetUrl("images/mobile/nexus5-playing.png"), - alt := "Lichess mobile on nexus 5" + widthA := "437", + heightA := "883", + cls := "mobile-playing", + src := assetUrl("images/mobile/lichesstv-mobile.png"), + alt := "Lichess TV on mobile" ), img( cls := "qrcode", - width := "200", - height := "200", + widthA := "200", + heightA := "200", src := assetUrl("images/mobile/dynamic-qrcode.png"), alt := "Download QR code" ) diff --git a/app/views/mod/menu.scala b/app/views/mod/menu.scala index ba5217e2a8..c1e072a63c 100644 --- a/app/views/mod/menu.scala +++ b/app/views/mod/menu.scala @@ -30,7 +30,7 @@ object menu { a(cls := active.active("tour"), href := routes.TournamentCrud.index(1))("Tournaments"), isGranted(_.ManageEvent) option a(cls := active.active("event"), href := routes.Event.manager)("Events"), - isGranted(_.SeeReport) option + isGranted(_.MarkEngine) option a(cls := active.active("irwin"), href := routes.Irwin.dashboard)("Irwin dashboard"), isGranted(_.Shadowban) option a(cls := active.active("panic"), href := routes.Mod.chatPanic)( diff --git a/app/views/oAuth/authorize.scala b/app/views/oAuth/authorize.scala index 2608f17e6c..028a04d9d8 100644 --- a/app/views/oAuth/authorize.scala +++ b/app/views/oAuth/authorize.scala @@ -17,7 +17,8 @@ object authorize { moreJs = embedJsUnsafe( // ensure maximum browser compatibility """setTimeout(function(){var el=document.getElementById('oauth-authorize');el.removeAttribute('disabled');el.setAttribute('class','button')}, 2000);""" - ) + ), + csp = defaultCsp.withLegacyCompatibility.some ) { main(cls := "oauth box box-pad")( div(cls := "oauth__top")( diff --git a/app/views/setup/forms.scala b/app/views/setup/forms.scala index 47eb144611..eca03be81a 100644 --- a/app/views/setup/forms.scala +++ b/app/views/setup/forms.scala @@ -167,14 +167,24 @@ object forms { div(cls := "ratings")( form3.hidden("rating", "?"), lila.rating.PerfType.nonPuzzle.map { perfType => - div(cls := perfType.key)( - trans.perfRatingX( - raw(s"""${ctx.pref.showRatings ?? me - .perfs(perfType.key) - .map(_.intRating.toString) - .getOrElse("?")} ${perfType.trans}""") + { + val rating = me + .perfs(perfType.key) + .map(_.intRating.toString) + .getOrElse("?") + div(cls := perfType.key)( + if (ctx.pref.showRatings) + trans.perfRatingX( + raw(s"""${rating} ${perfType.trans}""") + ) + else + frag( + i(dataIcon := perfType.iconChar), + strong(cls := "none")(rating), // To calculate rating range in JS + perfType.trans + ) ) - ) + } } ) } diff --git a/app/views/site/dailyPuzzleSlackApp.scala b/app/views/site/dailyPuzzleSlackApp.scala index d0543af4a8..0a1d0406bd 100644 --- a/app/views/site/dailyPuzzleSlackApp.scala +++ b/app/views/site/dailyPuzzleSlackApp.scala @@ -25,8 +25,8 @@ object dailyPuzzleSlackApp { )( img( alt := "Add to Slack", - height := 40, - width := 139, + heightA := 40, + widthA := 139, src := assetUrl("images/add-to-slack.png") ) ), diff --git a/app/views/swiss/show.scala b/app/views/swiss/show.scala index 97ad4d06bc..41f7a5a688 100644 --- a/app/views/swiss/show.scala +++ b/app/views/swiss/show.scala @@ -83,8 +83,7 @@ object show { def round(s: Swiss, r: SwissRound.Number, pairings: Paginator[SwissPairing])(implicit ctx: Context) = views.html.base.layout( title = s"${fullName(s)} • Round $r/${s.round}", - moreCss = cssTag("swiss.show"), - moreJs = infiniteScrollTag + moreCss = cssTag("swiss.show") ) { val pager = views.html.base.bits .pagination(p => routes.Swiss.round(s.id.value, p).url, r.value, s.round.value, showPost = true) @@ -95,7 +94,6 @@ object show { ), pager(cls := "pagination--top"), table(cls := "slist slist-pad")( - tbody(cls := "infinite-scroll")( pairings.currentPageResults map { p => tr(cls := "paginated")( td(a(href := routes.Round.watcher(p.gameId, "white"), cls := "glpt")(s"#${p.gameId}")), @@ -104,9 +102,7 @@ object show { td(p strResultOf chess.Black), td(userIdLink(p.black.some)) ) - }, - pagerNextTable(pairings, p => routes.Swiss.round(s.id.value, r.value).url) - ) + } ), pager(cls := "pagination--bottom") ) diff --git a/app/views/ublog/post.scala b/app/views/ublog/post.scala index 416dd7fb6b..5c9a34c362 100644 --- a/app/views/ublog/post.scala +++ b/app/views/ublog/post.scala @@ -73,7 +73,7 @@ object post { post.lived map { live => span(cls := "ublog-post__meta__date")(semanticDate(live.at)) }, - likeButton(post, liked)(ctx)(cls := "ublog-post__like--mini button-link"), + likeButton(post, liked, showText = false), span(cls := "ublog-post__views")( trans.ublog.nbViews.plural(post.views.value, strong(post.views.value.localize)) ), @@ -104,9 +104,7 @@ object post { div(cls := "ublog-post__footer")( (ctx.isAuth && !ctx.is(user)) option div(cls := "ublog-post__actions")( - likeButton(post, liked)(ctx)(cls := "ublog-post__like--big button button-big button-red")( - span(cls := "button-label")("Like this post") - ), + likeButton(post, liked, showText = true), followButton(user, post, followed) ), h2(a(href := routes.Ublog.index(user.username))(trans.ublog.moreBlogPostsBy(user.username))), @@ -122,15 +120,27 @@ object post { dataIcon := "" )(trans.edit()) - private def likeButton(post: UblogPost, liked: Boolean)(implicit ctx: Context) = button( - tpe := "button", - cls := List( - "ublog-post__like is" -> true, - "ublog-post__like--liked" -> liked - ), - dataRel := post.id.value, - title := trans.study.like.txt() - )(span(cls := "ublog-post__like__nb")(post.likes.value.localize)) + private def likeButton(post: UblogPost, liked: Boolean, showText: Boolean)(implicit ctx: Context) = { + val text = if (liked) trans.study.unlike.txt() else trans.study.like.txt() + button( + tpe := "button", + cls := List( + "ublog-post__like is" -> true, + "ublog-post__like--liked" -> liked, + "ublog-post__like--big button button-big button-red" -> showText, + "ublog-post__like--mini button-link" -> !showText + ), + dataRel := post.id.value, + title := text + )( + span(cls := "ublog-post__like__nb")(post.likes.value.localize), + showText option span( + cls := "button-label", + attr("data-i18n-like") := trans.study.like.txt(), + attr("data-i18n-unlike") := trans.study.unlike.txt() + )(text) + ) + } private def followButton(user: User, post: UblogPost, followed: Boolean)(implicit ctx: Context) = div( diff --git a/app/views/user/bots.scala b/app/views/user/bots.scala index 69665e0c36..ff54048974 100644 --- a/app/views/user/bots.scala +++ b/app/views/user/bots.scala @@ -37,9 +37,7 @@ object bots { private def botTable(users: List[User])(implicit ctx: Context) = table(cls := "slist slist-pad")( tbody( - users.sortBy { u => - (if (u.isVerified) -1 else 1, -u.playTime.??(_.total)) - } map { u => + users map { u => tr( td(userLink(u)), u.profile diff --git a/app/views/user/perfStat.scala b/app/views/user/perfStat.scala index 62441a7591..a16eddfb47 100644 --- a/app/views/user/perfStat.scala +++ b/app/views/user/perfStat.scala @@ -62,7 +62,7 @@ object perfStat { counter(stat.count), highlow(stat), resultStreak(stat.resultStreak), - result(stat), + result(stat, user), playStreakNb(stat.playStreak), playStreakTime(stat.playStreak) ) @@ -259,7 +259,9 @@ object perfStat { resultStreakSide(streak.loss, losingStreak(), "red") ) - private def resultTable(results: lila.perfStat.Results, title: Frag)(implicit lang: Lang): Frag = + private def resultTable(results: lila.perfStat.Results, title: Frag, user: User)(implicit + lang: Lang + ): Frag = div( table( thead( @@ -271,17 +273,21 @@ object perfStat { results.results map { r => tr( td(userIdLink(r.opId.value.some, withOnline = false), " (", r.opInt, ")"), - td(a(cls := "glpt", href := routes.Round.watcher(r.gameId, "white"))(absClientDateTime(r.at))) + td( + a(cls := "glpt", href := s"${routes.Round.watcher(r.gameId, "white")}?pov=${user.username}")( + absClientDateTime(r.at) + ) + ) ) } ) ) ) - private def result(stat: PerfStat)(implicit lang: Lang): Frag = + private def result(stat: PerfStat, user: User)(implicit lang: Lang): Frag = st.section(cls := "result split")( - resultTable(stat.bestWins, bestRated()), - resultTable(stat.worstLosses, worstRated()) + resultTable(stat.bestWins, bestRated(), user), + resultTable(stat.worstLosses, worstRated(), user) ) private def playStreakNbStreak(s: lila.perfStat.Streak, title: Frag => Frag)(implicit lang: Lang): Frag = diff --git a/app/views/user/show/otherTrophies.scala b/app/views/user/show/otherTrophies.scala index 0c1411e6dc..55cce07d48 100644 --- a/app/views/user/show/otherTrophies.scala +++ b/app/views/user/show/otherTrophies.scala @@ -46,7 +46,7 @@ object otherTrophies { ariaTitle(t.kind.name), style := "width: 65px; margin: 0 3px!important;" )( - img(src := assetUrl(s"images/trophy/${t.kind._id}.png"), width := 65, height := 80) + img(src := assetUrl(s"images/trophy/${t.kind._id}.png"), cssWidth := 65, cssHeight := 80) ) }, info.trophies.filter(_.kind.klass.has("icon3d")).sorted.map { trophy => diff --git a/conf/base.conf b/conf/base.conf index 7e4e91e862..29351430b5 100644 --- a/conf/base.conf +++ b/conf/base.conf @@ -108,9 +108,7 @@ pagerDuty { serviceId = "" apiKey = "" } -prismic { - api_url = "https://lichess.cdn.prismic.io/api" -} +prismic.api_url = "https://lichess-clone.cdn.prismic.io/api" blog { prismic = ${prismic} collection = blog diff --git a/conf/routes b/conf/routes index 4d5236e407..2fc8fe6e00 100644 --- a/conf/routes +++ b/conf/routes @@ -707,9 +707,10 @@ GET /api/user/:name/games controllers.Api.userGames(name: String) # Bot API GET /api/bot/game/stream/:id controllers.PlayApi.botGameStream(id: String) POST /api/bot/game/:id/move/:uci controllers.PlayApi.botMove(id: String, uci: String, offeringDraw: Option[Boolean] ?= None) -POST /api/bot/*cmd controllers.PlayApi.botCommand(cmd: String) -GET /player/bots controllers.PlayApi.botOnline GET /api/bot/online controllers.PlayApi.botOnlineApi +POST /api/bot/*cmd controllers.PlayApi.botCommand(cmd: String) +GET /api/bot/*cmd controllers.PlayApi.botCommandGet(cmd: String) +GET /player/bots controllers.PlayApi.botOnline # Board API GET /api/board/game/stream/:id controllers.PlayApi.boardGameStream(id: String) diff --git a/modules/activity/src/main/JsonView.scala b/modules/activity/src/main/JsonView.scala index 50595d060f..e8c151cd5f 100644 --- a/modules/activity/src/main/JsonView.scala +++ b/modules/activity/src/main/JsonView.scala @@ -6,7 +6,6 @@ import play.api.libs.json._ import lila.common.Iso import lila.common.Json._ -import lila.game.JsonView.colorWrites import lila.game.LightPov import lila.rating.PerfType import lila.simul.Simul diff --git a/modules/api/src/main/GameApi.scala b/modules/api/src/main/GameApi.scala index 3226de6426..c254507791 100644 --- a/modules/api/src/main/GameApi.scala +++ b/modules/api/src/main/GameApi.scala @@ -8,7 +8,7 @@ import reactivemongo.api.ReadPreference import lila.analyse.{ JsonView => analysisJson, Analysis } import lila.common.config._ -import lila.common.Json.jodaWrites +import lila.common.Json._ import lila.common.paginator.{ Paginator, PaginatorJson } import lila.db.dsl._ import lila.db.paginator.Adapter diff --git a/modules/api/src/main/GameApiV2.scala b/modules/api/src/main/GameApiV2.scala index 43693d81d8..6f038fd463 100644 --- a/modules/api/src/main/GameApiV2.scala +++ b/modules/api/src/main/GameApiV2.scala @@ -9,7 +9,7 @@ import scala.concurrent.duration._ import lila.analyse.{ JsonView => analysisJson, Analysis } import lila.common.config.MaxPerSecond -import lila.common.Json.jodaWrites +import lila.common.Json._ import lila.common.{ HTTPRequest, LightUser } import lila.db.dsl._ import lila.game.JsonView._ diff --git a/modules/blog/src/main/BlogApi.scala b/modules/blog/src/main/BlogApi.scala index 6fbade9a40..e53163eb03 100644 --- a/modules/blog/src/main/BlogApi.scala +++ b/modules/blog/src/main/BlogApi.scala @@ -6,6 +6,7 @@ import play.api.libs.ws.StandaloneWSClient import lila.common.config.MaxPerPage import lila.common.paginator._ +import scala.util.Try final class BlogApi( config: BlogConfig @@ -20,7 +21,7 @@ final class BlogApi( page: Int, maxPerPage: MaxPerPage, ref: Option[String] - ): Fu[Option[Paginator[Document]]] = + ): Fu[Option[Paginator[Document]]] = Try { api .forms(collection) .ref(ref | api.master.ref) @@ -30,6 +31,9 @@ final class BlogApi( .submit() .fold(_ => none, some) .dmap2 { PrismicPaginator(_, page, maxPerPage) } + } recover { case _: NoSuchElementException => + fuccess(none) + } get def recent( prismic: BlogApi.Context, @@ -94,9 +98,7 @@ final class BlogApi( } getOrElse reqRef } - private val prismicBuilder = new Prismic - - def prismicApi = prismicBuilder.get(config.apiUrl) + def prismicApi = (new Prismic).get(config.apiUrl) } object BlogApi { diff --git a/modules/bot/src/main/BotJsonView.scala b/modules/bot/src/main/BotJsonView.scala index 2f42fbf4ef..bc80dc65ca 100644 --- a/modules/bot/src/main/BotJsonView.scala +++ b/modules/bot/src/main/BotJsonView.scala @@ -3,6 +3,7 @@ package lila.bot import play.api.i18n.Lang import play.api.libs.json._ +import lila.common.Json._ import lila.common.Json.jodaWrites import lila.game.JsonView._ import lila.game.{ Game, GameRepo, Pov } diff --git a/modules/challenge/src/main/JsonView.scala b/modules/challenge/src/main/JsonView.scala index 3401b188d3..814ccf5ef3 100644 --- a/modules/challenge/src/main/JsonView.scala +++ b/modules/challenge/src/main/JsonView.scala @@ -1,8 +1,10 @@ package lila.challenge -import play.api.libs.json._ import play.api.i18n.Lang +import play.api.libs.json._ +import lila.common.Json._ +import lila.game.JsonView._ import lila.i18n.{ I18nKeys => trans } import lila.socket.Socket.SocketVersion import lila.socket.UserLagCache @@ -13,7 +15,6 @@ final class JsonView( isOnline: lila.socket.IsOnline ) { - import lila.game.JsonView._ import Challenge._ implicit private val RegisteredWrites = OWrites[Challenger.Registered] { r => diff --git a/modules/common/src/main/ContentSecurityPolicy.scala b/modules/common/src/main/ContentSecurityPolicy.scala index 7677142576..666b18aa07 100644 --- a/modules/common/src/main/ContentSecurityPolicy.scala +++ b/modules/common/src/main/ContentSecurityPolicy.scala @@ -11,11 +11,9 @@ case class ContentSecurityPolicy( baseUri: List[String] ) { - def withNonce(nonce: Nonce) = copy(scriptSrc = - nonce.scriptSrc :: - "'unsafe-inline'" :: // ignored by browsers supporting nonce - scriptSrc - ) + def withNonce(nonce: Nonce) = copy(scriptSrc = nonce.scriptSrc :: scriptSrc) + + def withLegacyCompatibility = copy(scriptSrc = "'unsafe-inline'" :: scriptSrc) def withWebAssembly = copy( diff --git a/modules/common/src/main/HTTPRequest.scala b/modules/common/src/main/HTTPRequest.scala index d5a5361fb6..a270b82423 100644 --- a/modules/common/src/main/HTTPRequest.scala +++ b/modules/common/src/main/HTTPRequest.scala @@ -45,6 +45,7 @@ object HTTPRequest { private def uaContains(req: RequestHeader, str: String) = userAgent(req).exists(_ contains str) def isChrome(req: RequestHeader) = uaContains(req, "Chrome/") + val isChrome96OrMore = UaMatcher("""Chrome/(?:\d{3,}|9[6-9])""") def origin(req: RequestHeader): Option[String] = req.headers get HeaderNames.ORIGIN @@ -58,7 +59,7 @@ object HTTPRequest { def sid(req: RequestHeader): Option[String] = req.session get LilaCookie.sessionId val isCrawler = UaMatcher { - """(?i)googlebot|googlebot-mobile|googlebot-image|mediapartners-google|bingbot|slurp|java|wget|curl|commons-httpclient|python-urllib|libwww|httpunit|nutch|phpcrawl|msnbot|adidxbot|blekkobot|teoma|ia_archiver|gingercrawler|webmon|httrack|webcrawler|fast-webcrawler|fastenterprisecrawler|convera|biglotron|grub\.org|usinenouvellecrawler|antibot|netresearchserver|speedy|fluffy|jyxobot|bibnum\.bnf|findlink|exabot|gigabot|msrbot|seekbot|ngbot|panscient|yacybot|aisearchbot|ioi|ips-agent|tagoobot|mj12bot|dotbot|woriobot|yanga|buzzbot|mlbot|purebot|lingueebot|yandex\.com/bots|""" + + """(?i)googlebot|googlebot-mobile|googlebot-image|mediapartners-google|bingbot|slurp|java|wget|curl|python-requests|commons-httpclient|python-urllib|libwww|httpunit|nutch|phpcrawl|msnbot|adidxbot|blekkobot|teoma|ia_archiver|gingercrawler|webmon|httrack|webcrawler|fast-webcrawler|fastenterprisecrawler|convera|biglotron|grub\.org|usinenouvellecrawler|antibot|netresearchserver|speedy|fluffy|jyxobot|bibnum\.bnf|findlink|exabot|gigabot|msrbot|seekbot|ngbot|panscient|yacybot|aisearchbot|ioi|ips-agent|tagoobot|mj12bot|dotbot|woriobot|yanga|buzzbot|mlbot|purebot|lingueebot|yandex\.com/bots|""" + """voyager|cyberpatrol|voilabot|baiduspider|citeseerxbot|spbot|twengabot|postrank|turnitinbot|scribdbot|page2rss|sitebot|linkdex|ezooms|dotbot|mail\.ru|discobot|zombie\.js|heritrix|findthatfile|europarchive\.org|nerdbynature\.bot|sistrixcrawler|ahrefsbot|aboundex|domaincrawler|wbsearchbot|summify|ccbot|edisterbot|seznambot|ec2linkfinder|gslfbot|aihitbot|intelium_bot|yeti|retrevopageanalyzer|lb-spider|sogou|lssbot|careerbot|wotbox|wocbot|ichiro|duckduckbot|lssrocketcrawler|drupact|webcompanycrawler|acoonbot|openindexspider|gnamgnamspider|web-archive-net\.com\.bot|backlinkcrawler|""" + """coccoc|integromedb|contentcrawlerspider|toplistbot|seokicks-robot|it2media-domain-crawler|ip-web-crawler\.com|siteexplorer\.info|elisabot|proximic|changedetection|blexbot|arabot|wesee:search|niki-bot|crystalsemanticsbot|rogerbot|360spider|psbot|interfaxscanbot|lipperheyseoservice|ccmetadatascaper|g00g1e\.net|grapeshotcrawler|urlappendbot|brainobot|fr-crawler|binlar|simplecrawler|simplecrawler|livelapbot|twitterbot|cxensebot|smtbot|facebookexternalhit|daumoa|sputnikimagebot|visionutils|yisouspider|parsijoobot|mediatoolkit\.com|semrushbot""" } diff --git a/modules/common/src/main/Json.scala b/modules/common/src/main/Json.scala index 98167b21ce..41d0027c74 100644 --- a/modules/common/src/main/Json.scala +++ b/modules/common/src/main/Json.scala @@ -41,9 +41,16 @@ object Json { JsNumber(time.getMillis) } + implicit val colorWrites: Writes[chess.Color] = Writes { c => + JsString(c.name) + } + implicit val fenFormat: Format[FEN] = stringIsoFormat[FEN](Iso.fenIso) - implicit val uciReader: Reads[Uci] = Reads.of[String] flatMapResult { str => + implicit val uciReads: Reads[Uci] = Reads.of[String] flatMapResult { str => JsResult.fromTry(Uci(str) toTry s"Invalid UCI: $str") } + implicit val uciWrites: Writes[Uci] = Writes { u => + JsString(u.uci) + } } diff --git a/modules/common/src/main/Markdown.scala b/modules/common/src/main/Markdown.scala index b837917388..6384ca4a5a 100644 --- a/modules/common/src/main/Markdown.scala +++ b/modules/common/src/main/Markdown.scala @@ -60,11 +60,14 @@ final class Markdown( private def mentionsToLinks(markdown: Text): Text = RawHtml.atUsernameRegex.replaceAllIn(markdown, "[$1](/@/$1)") + private val tooManyUnderscoreRegex = """(_{4,})""".r + private def preventStackOverflow(text: String) = tooManyUnderscoreRegex.replaceAllIn(text, "_" * 3) + def apply(key: Key)(text: Text): Html = Chronometer .sync { try { - addLinkAttributes(renderer.render(parser.parse(mentionsToLinks(text)))) + addLinkAttributes(renderer.render(parser.parse(mentionsToLinks(preventStackOverflow(text))))) } catch { case e: StackOverflowError => logger.branch(key).error("StackOverflowError", e) diff --git a/modules/common/src/main/Random.scala b/modules/common/src/main/Random.scala index 3fe0a0e128..e03c899056 100644 --- a/modules/common/src/main/Random.scala +++ b/modules/common/src/main/Random.scala @@ -38,10 +38,17 @@ abstract class Random { vec.nonEmpty ?? { vec lift nextInt(vec.size) } + + // odds(1) = 100% true + // odds(2) = 50% true + // odds(3) = 33% true + def odds(n: Int): Boolean = nextInt(n) == 0 } object ThreadLocalRandom extends Random { override def current = java.util.concurrent.ThreadLocalRandom.current + + def nextLong(n: Long): Long = current.nextLong(n) } object SecureRandom extends Random { diff --git a/modules/common/src/main/base/PimpedUtils.scala b/modules/common/src/main/base/PimpedUtils.scala index 7a86a93ad5..d42bb5d286 100644 --- a/modules/common/src/main/base/PimpedUtils.scala +++ b/modules/common/src/main/base/PimpedUtils.scala @@ -24,8 +24,6 @@ final class PimpedOption[A](private val self: Option[A]) extends AnyVal { def err(message: => String): A = self.getOrElse(sys.error(message)) - def ifNone(n: => Unit): Unit = if (self.isEmpty) n - def has(a: A) = self contains a } diff --git a/modules/common/src/main/mon.scala b/modules/common/src/main/mon.scala index f5e1cb6a28..407db66086 100644 --- a/modules/common/src/main/mon.scala +++ b/modules/common/src/main/mon.scala @@ -646,9 +646,15 @@ object mon { def request(hit: Boolean) = counter("fishnet.http.acquire").withTag("hit", hit) } def move(level: Int) = counter("fishnet.move.time").withTag("level", level) - def openingBook(level: Int, variant: String, ply: Int, hit: Boolean) = + def openingBook(level: Int, variant: String, ply: Int, hit: Boolean, success: Boolean) = timer("fishnet.opening.hit").withTags( - Map("level" -> level, "variant" -> variant, "ply" -> ply, "hit" -> hit) + Map( + "level" -> level.toLong, + "variant" -> variant, + "ply" -> ply.toLong, + "hit" -> hitTag(hit), + "success" -> successTag(success) + ) ) } object study { @@ -727,6 +733,7 @@ object mon { ) private def successTag(success: Boolean) = if (success) "success" else "failure" + private def hitTag(hit: Boolean) = if (hit) "hit" else "miss" private def apiTag(api: Option[ApiVersion]) = api.fold("-")(_.toString) diff --git a/modules/db/src/main/dsl.scala b/modules/db/src/main/dsl.scala index ab3933eabc..210453e6f6 100644 --- a/modules/db/src/main/dsl.scala +++ b/modules/db/src/main/dsl.scala @@ -63,6 +63,10 @@ trait dsl { def $nor(expressions: Bdoc*): Bdoc = { $doc("$nor" -> expressions) } + + def $not(expression: Bdoc): Bdoc = { + $doc("$not" -> expression) + } // End of Top Level Logical Operators //**********************************************************************************************// @@ -291,13 +295,6 @@ trait dsl { } - trait LogicalOperators { self: ElementBuilder => - def $not(f: String => Expression[Bdoc]): SimpleExpression[Bdoc] = { - val expression = f(field) - SimpleExpression(field, $doc("$not" -> expression.value)) - } - } - trait ElementOperators { self: ElementBuilder => def $exists(v: Boolean): SimpleExpression[Bdoc] = { SimpleExpression(field, $doc("$exists" -> v)) @@ -381,7 +378,6 @@ trait dsl { with ComparisonOperators with ElementOperators with EvaluationOperators - with LogicalOperators with ArrayOperators implicit def toBSONDocument[V: BSONWriter](expression: Expression[V]): Bdoc = diff --git a/modules/evalCache/src/main/Env.scala b/modules/evalCache/src/main/Env.scala index 6ea164e8f4..97c5df1c7f 100644 --- a/modules/evalCache/src/main/Env.scala +++ b/modules/evalCache/src/main/Env.scala @@ -16,6 +16,7 @@ final class Env( userRepo: lila.user.UserRepo, yoloDb: lila.db.AsyncDb @@ lila.db.YoloDb, cacheApi: lila.memo.CacheApi, + settingStore: lila.memo.SettingStore.Builder, scheduler: akka.actor.Scheduler )(implicit ec: scala.concurrent.ExecutionContext, @@ -27,6 +28,12 @@ final class Env( private lazy val truster = wire[EvalCacheTruster] + lazy val enable = settingStore[Boolean]( + "useCeval", + default = true, + text = "Enable cloud eval (disable in case of server trouble)".some + ) + private lazy val upgrade = wire[EvalCacheUpgrade] lazy val api: EvalCacheApi = wire[EvalCacheApi] diff --git a/modules/evalCache/src/main/EvalCacheApi.scala b/modules/evalCache/src/main/EvalCacheApi.scala index 0041cd0259..2299c46415 100644 --- a/modules/evalCache/src/main/EvalCacheApi.scala +++ b/modules/evalCache/src/main/EvalCacheApi.scala @@ -9,13 +9,16 @@ import scala.concurrent.duration._ import lila.db.AsyncCollFailingSilently import lila.db.dsl._ import lila.memo.CacheApi._ +import lila.memo.SettingStore import lila.socket.Socket +import lila.user.User final class EvalCacheApi( coll: AsyncCollFailingSilently, truster: EvalCacheTruster, upgrade: EvalCacheUpgrade, - cacheApi: lila.memo.CacheApi + cacheApi: lila.memo.CacheApi, + setting: SettingStore[Boolean] )(implicit ec: scala.concurrent.ExecutionContext) { import EvalCacheEntry._ @@ -36,7 +39,7 @@ final class EvalCacheApi( def put(trustedUser: TrustedUser, candidate: Input.Candidate, sri: Socket.Sri): Funit = candidate.input ?? { put(trustedUser, _, sri) } - def shouldPut = truster shouldPut _ + def shouldPut(user: User) = setting.get() && truster.shouldPut(user) def getSinglePvEval(variant: Variant, fen: FEN): Fu[Option[Eval]] = getEval( diff --git a/modules/evalCache/src/main/EvalCacheUpgrade.scala b/modules/evalCache/src/main/EvalCacheUpgrade.scala index 1b3066d623..d0cd72800b 100644 --- a/modules/evalCache/src/main/EvalCacheUpgrade.scala +++ b/modules/evalCache/src/main/EvalCacheUpgrade.scala @@ -9,38 +9,44 @@ import lila.socket.Socket import lila.memo.ExpireCallbackMemo import scala.collection.mutable +import lila.memo.SettingStore /* Upgrades the user's eval when a better one becomes available, * by remembering the last evalGet of each socket member, * and listening to new evals stored. */ -final private class EvalCacheUpgrade(scheduler: akka.actor.Scheduler)(implicit +final private class EvalCacheUpgrade(setting: SettingStore[Boolean], scheduler: akka.actor.Scheduler)(implicit ec: scala.concurrent.ExecutionContext, mode: play.api.Mode ) { import EvalCacheUpgrade._ private val members = mutable.AnyRefMap.empty[SriString, WatchingMember] - private val evals = mutable.AnyRefMap.empty[SetupId, Set[SriString]] - private val expirableSris = new ExpireCallbackMemo(20 minutes, sri => unregister(Socket.Sri(sri))) + private val evals = mutable.AnyRefMap.empty[SetupId, EvalState] + private val expirableSris = new ExpireCallbackMemo(10 minutes, sri => unregister(Socket.Sri(sri))) private val upgradeMon = lila.mon.evalCache.upgrade - def register(sri: Socket.Sri, variant: Variant, fen: FEN, multiPv: Int, path: String)(push: Push): Unit = { - members get sri.value foreach { wm => - unregisterEval(wm.setupId, sri) + def register(sri: Socket.Sri, variant: Variant, fen: FEN, multiPv: Int, path: String)(push: Push): Unit = + if (setting.get()) { + members get sri.value foreach { wm => + unregisterEval(wm.setupId, sri) + } + val setupId = makeSetupId(variant, fen, multiPv) + members += (sri.value -> WatchingMember(push, setupId, path)) + evals += (setupId -> evals.get(setupId).fold(EvalState(Set(sri.value), 0))(_ addSri sri)) + expirableSris put sri.value } - val setupId = makeSetupId(variant, fen, multiPv) - members += (sri.value -> WatchingMember(push, setupId, path)) - evals += (setupId -> (~evals.get(setupId) + sri.value)) - expirableSris put sri.value - } - def onEval(input: EvalCacheEntry.Input, sri: Socket.Sri): Unit = { + def onEval(input: EvalCacheEntry.Input, sri: Socket.Sri): Unit = if (setting.get()) { (1 to input.eval.multiPv) flatMap { multiPv => - evals get makeSetupId(input.id.variant, input.fen, multiPv) - } foreach { sris => - val wms = sris.withFilter(sri.value !=) flatMap members.get + val setupId = makeSetupId(input.id.variant, input.fen, multiPv) + evals get setupId map (setupId -> _) + } filter { + _._2.depth < input.eval.depth + } foreach { case (setupId, eval) => + evals += (setupId -> eval.copy(depth = input.eval.depth)) + val wms = eval.sris.withFilter(sri.value !=) flatMap members.get if (wms.nonEmpty) { val json = JsonHandlers.writeEval(input.eval, input.fen) wms foreach { wm => @@ -59,10 +65,10 @@ final private class EvalCacheUpgrade(scheduler: akka.actor.Scheduler)(implicit } private def unregisterEval(setupId: SetupId, sri: Socket.Sri): Unit = - evals get setupId foreach { sris => - val newSris = sris - sri.value + evals get setupId foreach { eval => + val newSris = eval.sris - sri.value if (newSris.isEmpty) evals -= setupId - else evals += (setupId -> newSris) + else evals += (setupId -> eval.copy(sris = newSris)) } scheduler.scheduleWithFixedDelay(1 minute, 1 minute) { () => @@ -78,6 +84,10 @@ private object EvalCacheUpgrade { private type SetupId = String private type Push = JsObject => Unit + private case class EvalState(sris: Set[SriString], depth: Int) { + def addSri(sri: Socket.Sri) = copy(sris = sris + sri.value) + } + private def makeSetupId(variant: Variant, fen: FEN, multiPv: Int): SetupId = s"${variant.id}${EvalCacheEntry.SmallFen.make(variant, fen).value}^$multiPv" diff --git a/modules/explorer/src/main/ExplorerIndexer.scala b/modules/explorer/src/main/ExplorerIndexer.scala index 722d4951e4..576027ceff 100644 --- a/modules/explorer/src/main/ExplorerIndexer.scala +++ b/modules/explorer/src/main/ExplorerIndexer.scala @@ -71,21 +71,22 @@ final private class ExplorerIndexer( case Correspondence | Classical => 1.00f case Rapid if rating >= 2200 => 1.00f - case Rapid if rating >= 2000 => 0.50f - case Rapid if rating >= 1800 => 0.28f - case Rapid if rating >= 1600 => 0.24f + case Rapid if rating >= 2000 => 0.83f + case Rapid if rating >= 1800 => 0.46f + case Rapid if rating >= 1600 => 0.39f case Rapid => 0.02f case Blitz if rating >= 2500 => 1.00f - case Blitz if rating >= 2200 => 0.24f - case Blitz if rating >= 2000 => 0.11f - case Blitz if rating >= 1600 => 0.08f + case Blitz if rating >= 2200 => 0.38f + case Blitz if rating >= 2000 => 0.18f + case Blitz if rating >= 1600 => 0.13f case Blitz => 0.02f case Bullet if rating >= 2500 => 1.00f - case Bullet if rating >= 2200 => 0.30f - case Bullet if rating >= 2000 => 0.17f - case Bullet if rating >= 1600 => 0.11f + case Bullet if rating >= 2200 => 0.48f + case Bullet if rating >= 2000 => 0.27f + case Bullet if rating >= 1800 => 0.19f + case Bullet if rating >= 1600 => 0.18f case Bullet => 0.02f case UltraBullet => 1.00f diff --git a/modules/fishnet/src/main/Analyser.scala b/modules/fishnet/src/main/Analyser.scala index 68d5d40212..c9e0f98cc8 100644 --- a/modules/fishnet/src/main/Analyser.scala +++ b/modules/fishnet/src/main/Analyser.scala @@ -18,7 +18,7 @@ final class Analyser( system: akka.actor.ActorSystem ) { - val maxPlies = 200 + val maxPlies = 300 private val workQueue = new lila.hub.AsyncActorSequencer(maxSize = 256, timeout = 5 seconds, "fishnetAnalyser") diff --git a/modules/fishnet/src/main/FishnetOpeningBook.scala b/modules/fishnet/src/main/FishnetOpeningBook.scala index e7add7e163..48c15494cd 100644 --- a/modules/fishnet/src/main/FishnetOpeningBook.scala +++ b/modules/fishnet/src/main/FishnetOpeningBook.scala @@ -2,17 +2,19 @@ package lila.fishnet import chess.format.Forsyth import chess.format.Uci -import chess.Speed +import chess.{ Color, Speed } import com.softwaremill.tagging._ import play.api.libs.json._ import play.api.libs.ws.JsonBodyReadables._ import play.api.libs.ws.StandaloneWSClient import scala.concurrent.ExecutionContext +import scala.concurrent.duration._ -import lila.common.Json.uciReader +import lila.common.Json._ import lila.common.ThreadLocalRandom import lila.game.Game import lila.memo.SettingStore +import scala.util.{ Failure, Success } final private class FishnetOpeningBook( ws: StandaloneWSClient, @@ -21,32 +23,42 @@ final private class FishnetOpeningBook( import FishnetOpeningBook._ - def apply(game: Game, level: Int): Fu[Option[Uci]] = (game.turns < depth.get()) ?? { - ws.url(endpoint) - .withQueryStringParameters( - "variant" -> game.variant.key, - "fen" -> Forsyth.>>(game.chess).value, - "topGames" -> "0", - "recentGames" -> "0", - "ratings" -> (~levelRatings.get(level)).mkString(","), - "speeds" -> (~openingSpeeds.get(game.speed)).map(_.key).mkString(",") - ) - .get() - .map { - case res if res.status != 200 => - logger.warn(s"opening book ${game.id} ${level} ${res.status} ${res.body}") - none - case res => - for { - data <- res.body[JsValue].validate[Response](responseReader).asOpt - move <- data.randomPonderedMove - } yield move.uci - } - .monValue(uci => - _.fishnet - .openingBook(level = level, variant = game.variant.key, ply = game.turns, hit = uci.isDefined) - ) - } + private val outOfBook = new lila.memo.ExpireSetMemo(10 minutes) + + def apply(game: Game, level: Int): Fu[Option[Uci]] = + (game.turns < depth.get() && !outOfBook.get(game.id)) ?? { + ws.url(endpoint) + .withQueryStringParameters( + "variant" -> game.variant.key, + "fen" -> Forsyth.>>(game.chess).value, + "topGames" -> "0", + "recentGames" -> "0", + "ratings" -> (~levelRatings.get(level)).mkString(","), + "speeds" -> (~openingSpeeds.get(game.speed)).map(_.key).mkString(",") + ) + .get() + .map { + case res if res.status != 200 => + logger.warn(s"opening book ${game.id} ${level} ${res.status} ${res.body}") + none + case res => + for { + data <- res.body[JsValue].validate[Response](responseReader).asOpt + _ = if (data.moves.isEmpty) outOfBook.put(game.id) + move <- data randomPonderedMove (game.turnColor, level) + } yield move.uci + } + .monTry { res => + _.fishnet + .openingBook( + level = level, + variant = game.variant.key, + ply = game.turns, + hit = res.toOption.exists(_.isDefined), + success = res.isSuccess + ) + } + } } object FishnetOpeningBook { @@ -54,20 +66,26 @@ object FishnetOpeningBook { trait Depth case class Response(moves: List[Move]) { - def randomPonderedMove: Option[Move] = { - val sum = moves.map(_.nb).sum - val rng = ThreadLocalRandom nextInt sum + + def randomPonderedMove(turn: Color, level: Int): Option[Move] = { + val sum = moves.map(_.score(turn, level)).sum + val novelty = 5L * 14 // score of 5 winning games + val rng = ThreadLocalRandom.nextLong(sum + novelty) moves - .foldLeft((none[Move], 0)) { case ((found, it), next) => - val nextIt = it + next.nb + .foldLeft((none[Move], 0L)) { case ((found, it), next) => + val nextIt = it + next.score(turn, level) (found orElse (nextIt > rng).option(next), nextIt) } ._1 } } - case class Move(uci: Uci, white: Int, draws: Int, black: Int) { - def nb = white + draws + black + case class Move(uci: Uci, white: Long, draws: Long, black: Long) { + def score(turn: Color, level: Int): Long = + // interpolate: real frequency at lvl 1, expectation value at lvl 8 + 14L * turn.fold(white, black) + + (15L - level) * draws + + (16L - 2 * level) * turn.fold(black, white) } implicit val moveReader = Json.reads[Move] diff --git a/modules/fishnet/src/main/FishnetPlayer.scala b/modules/fishnet/src/main/FishnetPlayer.scala index f37c0fc3fd..7c921ef8f4 100644 --- a/modules/fishnet/src/main/FishnetPlayer.scala +++ b/modules/fishnet/src/main/FishnetPlayer.scala @@ -1,15 +1,13 @@ package lila.fishnet +import chess.format.Uci +import chess.{ Black, Clock, White } import scala.concurrent.duration._ -import chess.{ Black, Clock, White } - -import lila.common.{ Future, ThreadLocalRandom } +import lila.common.{ Bus, Future, ThreadLocalRandom } import lila.game.{ Game, GameRepo, UciMemo } -import lila.common.Bus import lila.hub.actorApi.map.Tell import lila.hub.actorApi.round.FishnetPlay -import chess.format.Uci final class FishnetPlayer( redis: FishnetRedis, @@ -28,7 +26,7 @@ final class FishnetPlayer( openingBook(game, level) flatMap { case Some(move) => fuccess { - Bus.publish(Tell(game.id, FishnetPlay(move, game.turns)), "roundSocket") + Bus.publish(Tell(game.id, FishnetPlay(move, game.playedTurns)), "roundSocket") } case None => makeWork(game, level) addEffect redis.request void } diff --git a/modules/game/src/main/Cached.scala b/modules/game/src/main/Cached.scala index 5e9a45bf8c..ad2b9888e1 100644 --- a/modules/game/src/main/Cached.scala +++ b/modules/game/src/main/Cached.scala @@ -24,7 +24,7 @@ final class Cached( private val lastPlayedPlayingIdCache: LoadingCache[User.ID, Fu[Option[Game.ID]]] = CacheApi.scaffeineNoScheduler - .expireAfterWrite(5 seconds) + .expireAfterWrite(11 seconds) .build(gameRepo.lastPlayedPlayingId) lila.common.Bus.subscribeFun("startGame") { case lila.game.actorApi.StartGame(game) => diff --git a/modules/game/src/main/Captcher.scala b/modules/game/src/main/Captcher.scala index 9bca056886..c3d56c9e81 100644 --- a/modules/game/src/main/Captcher.scala +++ b/modules/game/src/main/Captcher.scala @@ -47,21 +47,16 @@ final private class Captcher(gameRepo: GameRepo)(implicit ec: scala.concurrent.E private val capacity = 256 private var challenges = NonEmptyList.one(Captcha.default) - private def add(c: Captcha): Unit = { - find(c.gameId) ifNone { + private def add(c: Captcha): Unit = + if (find(c.gameId).isEmpty) { challenges = NonEmptyList(c, challenges.toList take capacity) } - } private def find(id: String): Option[Captcha] = challenges.find(_.gameId == id) private def createFromDb: Fu[Option[Captcha]] = - findCheckmateInDb(10) flatMap { - _.fold(findCheckmateInDb(1))(g => fuccess(g.some)) - } flatMap { - _ ?? fromGame - } + findCheckmateInDb(10) orElse findCheckmateInDb(1) flatMap { _ ?? fromGame } private def findCheckmateInDb(distribution: Int): Fu[Option[Game]] = gameRepo findRandomStandardCheckmate distribution diff --git a/modules/game/src/main/Event.scala b/modules/game/src/main/Event.scala index 9c97ed0551..365c956ca8 100644 --- a/modules/game/src/main/Event.scala +++ b/modules/game/src/main/Event.scala @@ -17,6 +17,7 @@ import chess.{ import JsonView._ import lila.chat.{ PlayerLine, UserLine } import lila.common.ApiVersion +import lila.common.Json._ sealed trait Event { def typ: String diff --git a/modules/game/src/main/Game.scala b/modules/game/src/main/Game.scala index 5dbfe52efc..4bf24b8b44 100644 --- a/modules/game/src/main/Game.scala +++ b/modules/game/src/main/Game.scala @@ -555,7 +555,7 @@ case class Game( def abandoned = (status <= Status.Started) && { movedAt isBefore { - if (hasAi && !hasCorrespondenceClock) Game.aiAbandonedDate + if (hasAi && hasClock) Game.aiAbandonedDate else Game.abandonedDate } } diff --git a/modules/game/src/main/GameRepo.scala b/modules/game/src/main/GameRepo.scala index 292b74eb1c..ddc2f98bca 100644 --- a/modules/game/src/main/GameRepo.scala +++ b/modules/game/src/main/GameRepo.scala @@ -1,5 +1,7 @@ package lila.game +import scala.concurrent.duration._ + import chess.format.{ FEN, Forsyth } import chess.{ Color, Status } import org.joda.time.DateTime @@ -19,6 +21,8 @@ final class GameRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionCont import Game.{ ID, BSONFields => F } import Player.holdAlertBSONHandler + val fixedColorLobbyCache = new lila.memo.ExpireSetMemo(2 hours) + def game(gameId: ID): Fu[Option[Game]] = coll.byId[Game](gameId) def gameFromSecondary(gameId: ID): Fu[Option[Game]] = coll.secondaryPreferred.byId[Game](gameId) diff --git a/modules/game/src/main/JsonView.scala b/modules/game/src/main/JsonView.scala index ac32290eef..a20f80564c 100644 --- a/modules/game/src/main/JsonView.scala +++ b/modules/game/src/main/JsonView.scala @@ -5,7 +5,7 @@ import play.api.libs.json._ import chess.format.{ FEN, Forsyth } import chess.variant.Crazyhouse import chess.{ Clock, Color } -import lila.common.Json.jodaWrites +import lila.common.Json._ final class JsonView(rematches: Rematches) { @@ -143,12 +143,4 @@ object JsonView { implicit val sourceWriter: Writes[Source] = Writes { s => JsString(s.name) } - - implicit val colorWrites: Writes[Color] = Writes { c => - JsString(c.name) - } - - implicit val fenWrites: Writes[FEN] = Writes { f => - JsString(f.value) - } } diff --git a/modules/game/src/main/PgnDump.scala b/modules/game/src/main/PgnDump.scala index 6c51281333..60218cf8dd 100644 --- a/modules/game/src/main/PgnDump.scala +++ b/modules/game/src/main/PgnDump.scala @@ -57,10 +57,12 @@ final class PgnDump( private def gameLightUsers(game: Game): Fu[(Option[LightUser], Option[LightUser])] = (game.whitePlayer.userId ?? lightUserApi.async) zip (game.blackPlayer.userId ?? lightUserApi.async) - private def rating(p: Player) = p.rating.fold("?")(_.toString) + private def rating(p: Player) = p.rating.orElse(p.nameSplit.flatMap(_._2)).fold("?")(_.toString) def player(p: Player, u: Option[LightUser]) = - p.aiLevel.fold(u.fold(p.name | lila.user.User.anonymous)(_.name))("lichess AI level " + _) + p.aiLevel.fold(u.fold(p.nameSplit.map(_._1).orElse(p.name) | lila.user.User.anonymous)(_.name))( + "lichess AI level " + _ + ) private val customStartPosition: Set[chess.variant.Variant] = Set(chess.variant.Chess960, chess.variant.FromPosition, chess.variant.Horde, chess.variant.RacingKings) diff --git a/modules/game/src/main/Player.scala b/modules/game/src/main/Player.scala index cf3eed3302..1d21d22530 100644 --- a/modules/game/src/main/Player.scala +++ b/modules/game/src/main/Player.scala @@ -60,8 +60,8 @@ case class Player( def nameSplit: Option[(String, Option[Int])] = name map { - case Player.nameSplitRegex(n, r) => n -> r.toIntOption - case n => n -> none + case Player.nameSplitRegex(n, r) => n.trim -> r.toIntOption + case n => n -> none } def before(other: Player) = diff --git a/modules/insight/src/main/Share.scala b/modules/insight/src/main/Share.scala index 1bbf212fde..dd19566b27 100644 --- a/modules/insight/src/main/Share.scala +++ b/modules/insight/src/main/Share.scala @@ -9,20 +9,18 @@ final class Share( relationApi: lila.relation.RelationApi )(implicit ec: scala.concurrent.ExecutionContext) { - def getPrefId(insighted: User) = prefApi.getPrefById(insighted.id) dmap (_.insightShare) + def getPrefId(insighted: User) = prefApi.getPref(insighted.id, _.insightShare) def grant(insighted: User, to: Option[User]): Fu[Boolean] = if (to ?? Granter(_.SeeInsight)) fuTrue else - prefApi.getPrefById(insighted.id) flatMap { pref => - pref.insightShare match { - case _ if to.contains(insighted) => fuTrue - case Pref.InsightShare.EVERYBODY => fuTrue - case Pref.InsightShare.FRIENDS => - to ?? { t => - relationApi.fetchAreFriends(insighted.id, t.id) - } - case Pref.InsightShare.NOBODY => fuFalse - } + getPrefId(insighted) flatMap { + case _ if to.contains(insighted) => fuTrue + case Pref.InsightShare.EVERYBODY => fuTrue + case Pref.InsightShare.FRIENDS => + to ?? { t => + relationApi.fetchAreFriends(insighted.id, t.id) + } + case Pref.InsightShare.NOBODY => fuFalse } } diff --git a/modules/irc/src/main/IrcApi.scala b/modules/irc/src/main/IrcApi.scala index d12ac2a037..4a3a312ce2 100644 --- a/modules/irc/src/main/IrcApi.scala +++ b/modules/irc/src/main/IrcApi.scala @@ -38,7 +38,7 @@ final class IrcApi( ) case Some(note) => zulip.sendAndGetLink(stream, "/" + user.username)( - s"${markdown.modLink(mod.user.username)} :pepenote: **${markdown + s"${markdown.modLink(mod.user)} :pepenote: **${markdown .userLink(user.username)}** (${markdown.userNotesLink(user.username)}):\n" + markdown.linkifyUsers(note.text take 2000) ) @@ -70,7 +70,7 @@ final class IrcApi( def commlog(mod: Holder, user: User, reportBy: Option[User.ID]): Funit = zulip(_.mod.adminLog, "private comms checks")({ val finalS = if (user.username endsWith "s") "" else "s" - s"**${markdown modLink mod.user.username}** checked out **${markdown userLink user.username}**'$finalS communications " + s"**${markdown modLink mod.user}** checked out **${markdown userLink user.username}**'$finalS communications " } + reportBy.filter(mod.id !=).fold("spontaneously") { by => s"while investigating a report created by ${markdown.userLink(by)}" }) @@ -96,10 +96,11 @@ final class IrcApi( // def printBan(mod: Holder, print: String, userIds: List[User.ID]): Funit = // logMod(mod.id, "footprints", s"Ban print $print of ${userIds} users: ${userIds map linkifyUsers}") - def chatPanic(mod: Holder, v: Boolean): Funit = - zulip(_.mod.log, "chat panic")( + def chatPanic(mod: Holder, v: Boolean): Funit = { + val msg = s":stop: ${markdown.modLink(mod.user)} ${if (v) "enabled" else "disabled"} ${markdown.lichessLink("/mod/chat-panic", " Chat Panic")}" - ) + zulip(_.mod.log, "chat panic")(msg) >> zulip(_.mod.commsPublic, "main")(msg) + } def garbageCollector(msg: String): Funit = zulip(_.mod.adminLog, "garbage collector")(markdown linkifyUsers msg) @@ -132,6 +133,9 @@ final class IrcApi( } } + def nameClosePreset(username: String): Funit = + zulip(_.mod.commsPublic, "/" + username)("@**remind** here in 48h to close this account") + def stop(): Funit = zulip(_.general, "lila")("Lichess is restarting.") def publishEvent(event: Event): Funit = event match { @@ -208,7 +212,7 @@ object IrcApi { private object markdown { def link(url: String, name: String) = s"[$name]($url)" def lichessLink(path: String, name: String) = s"[$name](https://lichess.org$path)" - def userLink(name: String): String = lichessLink(s"/@/$name?mod", name) + def userLink(name: String): String = lichessLink(s"/@/$name?mod¬es", name) def userLink(user: User): String = userLink(user.username) def modLink(name: String): String = lichessLink(s"/@/$name", name) def modLink(user: User): String = modLink(user.username) diff --git a/modules/irc/src/main/ZulipClient.scala b/modules/irc/src/main/ZulipClient.scala index fbef967183..d15d6f1406 100644 --- a/modules/irc/src/main/ZulipClient.scala +++ b/modules/irc/src/main/ZulipClient.scala @@ -80,6 +80,7 @@ private object ZulipClient { val log = "mod-log" val adminLog = "mod-admin-log" val adminGeneral = "mod-admin-general" + val commsPublic = "mod-comms-public" val commsPrivate = "mod-comms-private" val hunterCheat = "mod-hunter-cheat" val adminAppeal = "mod-admin-appeal" diff --git a/modules/lobby/src/main/AbortListener.scala b/modules/lobby/src/main/AbortListener.scala index 54f521d59b..548cbf936b 100644 --- a/modules/lobby/src/main/AbortListener.scala +++ b/modules/lobby/src/main/AbortListener.scala @@ -4,6 +4,7 @@ import lila.game.{ Pov, Source } final private class AbortListener( userRepo: lila.user.UserRepo, + gameRepo: lila.game.GameRepo, seekApi: SeekApi, lobbyActor: LobbySyncActor )(implicit ec: scala.concurrent.ExecutionContext) { @@ -14,7 +15,10 @@ final private class AbortListener( lobbyActor.registerAbortedGame(pov.game) private def cancelColorIncrement(pov: Pov): Unit = - if (pov.game.source.exists(s => s == Source.Lobby || s == Source.Pool)) pov.game.userIds match { + if ( + pov.game.source + .exists(s => s == Source.Lobby || s == Source.Pool) && !gameRepo.fixedColorLobbyCache.get(pov.game.id) + ) pov.game.userIds match { case List(u1, u2) => userRepo.incColor(u1, -1) userRepo.incColor(u2, 1) diff --git a/modules/lobby/src/main/Biter.scala b/modules/lobby/src/main/Biter.scala index ce7c7b6038..ed1b755d45 100644 --- a/modules/lobby/src/main/Biter.scala +++ b/modules/lobby/src/main/Biter.scala @@ -36,6 +36,7 @@ final private class Biter( _ <- gameRepo insertDenormalized game } yield { lila.mon.lobby.hook.join.increment() + rememberIfFixedColor(hook.realColor, game) JoinHook(sri, hook, game, creatorColor) } @@ -50,7 +51,14 @@ final private class Biter( blackUser = creatorColor.fold(user.some, owner.some) ).withUniqueId _ <- gameRepo insertDenormalized game - } yield JoinSeek(user.id, seek, game, creatorColor) + } yield { + rememberIfFixedColor(seek.realColor, game) + JoinSeek(user.id, seek, game, creatorColor) + } + + private def rememberIfFixedColor(color: Color, game: Game) = + if (color != Color.Random) + gameRepo.fixedColorLobbyCache put game.id private def assignCreatorColor( creatorUser: Option[User], diff --git a/modules/mod/src/main/ModQueueStats.scala b/modules/mod/src/main/ModQueueStats.scala index 4b65053074..78bb497f41 100644 --- a/modules/mod/src/main/ModQueueStats.scala +++ b/modules/mod/src/main/ModQueueStats.scala @@ -51,11 +51,10 @@ final class ModQueueStats( data <- doc.getAsOpt[List[Bdoc]]("data") } yield date -> { for { - entry <- data - nb <- entry.int("nb") - roomStr <- entry.string("room") - room <- Room.byKey get roomStr - score <- entry.int("score") + entry <- data + nb <- entry.int("nb") + room <- entry.string("room") + score <- entry.int("score") } yield (room, score, nb) } } @@ -66,20 +65,25 @@ final class ModQueueStats( "common" -> Json.obj( "xaxis" -> days.map(_._1.getMillis) ), - "rooms" -> Room.all.map { room => - Json.obj( - "name" -> room.name, - "series" -> scores.collect { - case score if score > 20 || room == Room.Boost => - Json.obj( - "name" -> score, - "data" -> days.map(~_._2.collectFirst { - case (r, s, nb) if r == room && s == score => nb - }) - ) - } - ) - } + "rooms" -> Room.all + .map { room => + room.key -> room.name + } + .appended { ("appeal", "Appeal") } + .map { case (roomKey, roomName) => + Json.obj( + "name" -> roomName, + "series" -> scores.collect { + case score if score > 20 || roomKey == Room.Boost.key => + Json.obj( + "name" -> score, + "data" -> days.map(~_._2.collectFirst { + case (r, s, nb) if r == roomKey && s == score => nb + }) + ) + } + ) + } ) ) } diff --git a/modules/mod/src/main/ModlogApi.scala b/modules/mod/src/main/ModlogApi.scala index caa6ed94e8..f37f001fd8 100644 --- a/modules/mod/src/main/ModlogApi.scala +++ b/modules/mod/src/main/ModlogApi.scala @@ -321,7 +321,8 @@ final class ModlogApi(repo: ModlogRepo, userRepo: UserRepo, ircApi: IrcApi)(impl case M.engine | M.unengine | M.booster | M.unbooster | M.reopenAccount | M.unalt => Some(IrcApi.ModDomain.Hunt) case M.troll | M.untroll | M.chatTimeout | M.closeTopic | M.openTopic | M.disableTeam | - M.enableTeam | M.setKidMode | M.deletePost | M.postAsAnonMod | M.editAsAnonMod => + M.enableTeam | M.setKidMode | M.deletePost | M.postAsAnonMod | M.editAsAnonMod | M.blogTier | + M.blogPostEdit => Some(IrcApi.ModDomain.Comm) case _ => Some(IrcApi.ModDomain.Other) } diff --git a/modules/mod/src/main/Presets.scala b/modules/mod/src/main/Presets.scala index caf76ea09c..1fba16ac90 100644 --- a/modules/mod/src/main/Presets.scala +++ b/modules/mod/src/main/Presets.scala @@ -50,11 +50,15 @@ case class ModPresets(value: List[ModPreset]) { value.find(_.text.filter(_.isLetter) == clean) } } -case class ModPreset(name: String, text: String, permissions: Set[Permission]) +case class ModPreset(name: String, text: String, permissions: Set[Permission]) { + + def isNameClose = name contains ModPresets.nameClosePresetName +} object ModPresets { - val groups = List("PM", "appeal") + val groups = List("PM", "appeal") + val nameClosePresetName = "Account closure for name in 48h" private[mod] object setting { diff --git a/modules/perfStat/src/main/JsonView.scala b/modules/perfStat/src/main/JsonView.scala index a321865bb7..c273eb4bfe 100644 --- a/modules/perfStat/src/main/JsonView.scala +++ b/modules/perfStat/src/main/JsonView.scala @@ -5,6 +5,7 @@ import org.joda.time.format.ISODateTimeFormat import play.api.i18n.Lang import play.api.libs.json._ +import lila.common.Json.{ jodaWrites => _, _ } import lila.common.LightUser import lila.rating.{ Glicko, Perf, PerfType } import lila.user.User diff --git a/modules/plan/src/main/PlanApi.scala b/modules/plan/src/main/PlanApi.scala index 6ea1f74425..512e29ce5b 100644 --- a/modules/plan/src/main/PlanApi.scala +++ b/modules/plan/src/main/PlanApi.scala @@ -282,25 +282,24 @@ final class PlanApi( .void >> setDbUserPlanOnCharge(user, levelUp = false) def gift(from: User, to: User, money: Money): Funit = - !to.isPatron ?? { - for { - isLifetime <- pricingApi isLifetime money - _ <- patronColl.update - .one( - $id(to.id), - $set( - "lastLevelUp" -> DateTime.now, - "lifetime" -> isLifetime, - "free" -> Patron.Free(DateTime.now, by = from.id.some), - "expiresAt" -> (!isLifetime option DateTime.now.plusMonths(1)) - ), - upsert = true - ) - newTo = to.mapPlan(_.incMonths) - _ <- setDbUserPlan(newTo) - } yield { - notifier.onGift(from, newTo, isLifetime) - } + for { + toPatronOpt <- userPatron(to) + isLifetime <- fuccess(toPatronOpt.exists(_.isLifetime)) >>| (pricingApi isLifetime money) + _ <- patronColl.update + .one( + $id(to.id), + $set( + "lastLevelUp" -> DateTime.now, + "lifetime" -> isLifetime, + "free" -> Patron.Free(DateTime.now, by = from.id.some), + "expiresAt" -> (!isLifetime option DateTime.now.plusMonths(1)) + ), + upsert = true + ) + newTo = to.mapPlan(p => if (toPatronOpt.exists(_.canLevelUp)) p.incMonths else p.enable) + _ <- setDbUserPlan(newTo) + } yield { + notifier.onGift(from, newTo, isLifetime) } def recentGiftFrom(from: User): Fu[Option[Patron]] = diff --git a/modules/playban/src/main/PlaybanApi.scala b/modules/playban/src/main/PlaybanApi.scala index b19242a9a9..97dcac80d7 100644 --- a/modules/playban/src/main/PlaybanApi.scala +++ b/modules/playban/src/main/PlaybanApi.scala @@ -132,7 +132,7 @@ final class PlaybanApi( Status.Resign.is(status) } .map { c => - (c.estimateTotalSeconds / 10) atLeast 15 atMost (3 * 60) + (c.estimateTotalSeconds / 10) atLeast 30 atMost (3 * 60) } .exists(_ < nowSeconds - game.movedAt.getSeconds) .option { diff --git a/modules/pref/src/main/Pref.scala b/modules/pref/src/main/Pref.scala index 907c9f924e..de73719cad 100644 --- a/modules/pref/src/main/Pref.scala +++ b/modules/pref/src/main/Pref.scala @@ -1,5 +1,9 @@ package lila.pref +import org.joda.time.DateTime + +import lila.user.User + case class Pref( _id: String, // user id bg: Int, @@ -413,7 +417,14 @@ object Pref { object Zen extends BooleanPref {} object Ratings extends BooleanPref {} - def create(id: String) = default.copy(_id = id) + val darkByDefaultSince = new DateTime(2021, 11, 7, 8, 0) + + def create(id: User.ID) = default.copy(_id = id) + + def create(user: User) = default.copy( + _id = user.id, + bg = if (user.createdAt isAfter darkByDefaultSince) Bg.DARK else Bg.LIGHT + ) lazy val default = Pref( _id = "", diff --git a/modules/pref/src/main/PrefApi.scala b/modules/pref/src/main/PrefApi.scala index 3aee2d168c..b029cd4311 100644 --- a/modules/pref/src/main/PrefApi.scala +++ b/modules/pref/src/main/PrefApi.scala @@ -37,13 +37,16 @@ final class PrefApi( .void >>- { cache invalidate user.id } } >>- { cache invalidate user.id } - def getPrefById(id: User.ID): Fu[Pref] = cache get id dmap (_ getOrElse Pref.create(id)) - val getPref = getPrefById _ - def getPref(user: User): Fu[Pref] = getPref(user.id) - def getPref(user: Option[User]): Fu[Pref] = user.fold(fuccess(Pref.default))(getPref) + def getPrefById(id: User.ID): Fu[Option[Pref]] = cache get id - def getPref[A](user: User, pref: Pref => A): Fu[A] = getPref(user) dmap pref - def getPref[A](userId: User.ID, pref: Pref => A): Fu[A] = getPref(userId) dmap pref + def getPref(user: User): Fu[Pref] = cache get user.id dmap { + _ getOrElse Pref.create(user) + } + + def getPref[A](user: User, pref: Pref => A): Fu[A] = getPref(user) dmap pref + + def getPref[A](userId: User.ID, pref: Pref => A): Fu[A] = + getPrefById(userId).dmap(p => pref(p | Pref.default)) def getPref(user: User, req: RequestHeader): Fu[Pref] = getPref(user) dmap RequestPref.queryParamOverride(req) @@ -81,9 +84,6 @@ final class PrefApi( def setPref(user: User, change: Pref => Pref): Funit = getPref(user) map change flatMap setPref - def setPref(userId: User.ID, change: Pref => Pref): Funit = - getPref(userId) map change flatMap setPref - def setPrefString(user: User, name: String, value: String): Funit = getPref(user) map { _.set(name, value) } orFail s"Bad pref ${user.id} $name -> $value" flatMap setPref diff --git a/modules/pref/src/main/Theme.scala b/modules/pref/src/main/Theme.scala index a66d06c7fe..c4ebe4b211 100644 --- a/modules/pref/src/main/Theme.scala +++ b/modules/pref/src/main/Theme.scala @@ -1,13 +1,10 @@ package lila.pref -sealed class Theme private[pref] (val name: String, val colors: Theme.HexColors) { +sealed class Theme private[pref] (val name: String, val file: String) { override def toString = name def cssClass = name - - def light = colors._1 - def dark = colors._2 } sealed trait ThemeObject { @@ -27,49 +24,33 @@ sealed trait ThemeObject { object Theme extends ThemeObject { - case class HexColor(value: String) extends AnyVal with StringValue - type HexColors = (HexColor, HexColor) - - private[pref] val defaultHexColors = (HexColor("b0b0b0"), HexColor("909090")) - - private val colors: Map[String, HexColors] = Map( - "blue" -> (HexColor("dee3e6") -> HexColor("8ca2ad")), - "brown" -> (HexColor("f0d9b5") -> HexColor("b58863")), - "green" -> (HexColor("ffffdd") -> HexColor("86a666")), - "purple" -> (HexColor("9f90b0") -> HexColor("7d4a8d")), - "ic" -> (HexColor("ececec") -> HexColor("c1c18e")), - "horsey" -> (HexColor("f1d9b6") -> HexColor("8e6547")) - ) - val all = List( - "blue", - "blue2", - "blue3", - "blue-marble", - "canvas", - "wood", - "wood2", - "wood3", - "wood4", - "maple", - "maple2", - "brown", - "leather", - "green", - "marble", - "green-plastic", - "grey", - "metal", - "olive", - "newspaper", - "purple", - "purple-diag", - "pink", - "ic", - "horsey" - ) map { name => - new Theme(name, colors.getOrElse(name, defaultHexColors)) - } + new Theme("blue", "svg/blue.svg"), + new Theme("blue2", "blue2.jpg"), + new Theme("blue3", "blue3.jpg"), + new Theme("blue-marble", "blue-marble.jpg"), + new Theme("canvas", "canvas2.jpg"), + new Theme("wood", "wood.jpg"), + new Theme("wood2", "wood2.jpg"), + new Theme("wood3", "wood3.jpg"), + new Theme("wood4", "wood4.jpg"), + new Theme("maple", "maple.jpg"), + new Theme("maple2", "maple2.jpg"), + new Theme("brown", "svg/brown.svg"), + new Theme("leather", "leather.jpg"), + new Theme("green", "svg/green.svg"), + new Theme("marble", "marble.jpg"), + new Theme("green-plastic", "green-plastic.png"), + new Theme("grey", "grey.jpg"), + new Theme("metal", "metal.jpg"), + new Theme("olive", "olive.jpg"), + new Theme("newspaper", "newspaper.png"), + new Theme("purple", "svg/purple.svg"), + new Theme("purple-diag", "purple-diag.png"), + new Theme("pink", "pink-pyramid.png"), + new Theme("ic", "svg/ic.svg"), + new Theme("horsey", "horsey.jpg") + ) lazy val default = allByName get "brown" err "Can't find default theme D:" } @@ -77,28 +58,26 @@ object Theme extends ThemeObject { object Theme3d extends ThemeObject { val all = List( - "Black-White-Aluminium", - "Brushed-Aluminium", - "China-Blue", - "China-Green", - "China-Grey", - "China-Scarlet", - "China-Yellow", - "Classic-Blue", - "Gold-Silver", - "Green-Glass", - "Light-Wood", - "Power-Coated", - "Purple-Black", - "Rosewood", - "Wood-Glass", - "Marble", - "Wax", - "Jade", - "Woodi" - ) map { name => - new Theme(name, Theme.defaultHexColors) - } + new Theme("Black-White-Aluminium", "Black-White-Aluminium.png"), + new Theme("Brushed-Aluminium", "Brushed-Aluminium.png"), + new Theme("China-Blue", "China-Blue.png"), + new Theme("China-Green", "China-Green.png"), + new Theme("China-Grey", "China-Grey.png"), + new Theme("China-Scarlet", "China-Scarlet.png"), + new Theme("China-Yellow", "China-Yellow.png"), + new Theme("Classic-Blue", "Classic-Blue.png"), + new Theme("Gold-Silver", "Gold-Silver.png"), + new Theme("Green-Glass", "Green-Glass.png"), + new Theme("Light-Wood", "Light-Wood.png"), + new Theme("Power-Coated", "Power-Coated.png"), + new Theme("Purple-Black", "Purple-Black.png"), + new Theme("Rosewood", "Rosewood.png"), + new Theme("Wood-Glass", "Wood-Glass.png"), + new Theme("Marble", "Marble.png"), + new Theme("Wax", "Wax.png"), + new Theme("Jade", "Jade.png"), + new Theme("Woodi", "Woodi.png") + ) lazy val default = allByName get "Woodi" err "Can't find default theme D:" } diff --git a/modules/puzzle/src/main/DailyPuzzle.scala b/modules/puzzle/src/main/DailyPuzzle.scala index 8a237a3a3d..5a14cbc5d9 100644 --- a/modules/puzzle/src/main/DailyPuzzle.scala +++ b/modules/puzzle/src/main/DailyPuzzle.scala @@ -7,6 +7,7 @@ import scala.concurrent.duration._ import lila.db.dsl._ import lila.memo.CacheApi._ +import lila.common.ThreadLocalRandom final private[puzzle] class DailyPuzzle( colls: PuzzleColls, @@ -26,7 +27,7 @@ final private[puzzle] class DailyPuzzle( def get: Fu[Option[DailyPuzzle.WithHtml]] = cache.getUnit private def find: Fu[Option[DailyPuzzle.WithHtml]] = - (findCurrent orElse findNew) recover { case e: Exception => + (findCurrent orElse findNewBiased()) recover { case e: Exception => logger.error("find daily", e) none } flatMap { _ ?? makeDaily } @@ -48,6 +49,18 @@ final private[puzzle] class DailyPuzzle( .one[Puzzle] } + private def findNewBiased(tries: Int = 0): Fu[Option[Puzzle]] = { + def tryAgainMaybe = (tries < 5) ?? findNewBiased(tries + 1) + import lila.common.ThreadLocalRandom.odds + import PuzzleTheme._ + findNew flatMap { + case None => tryAgainMaybe + case Some(p) if p.hasTheme(anastasiaMate) && !odds(3) => tryAgainMaybe dmap (_ orElse p.some) + case Some(p) if p.hasTheme(arabianMate) && odds(2) => tryAgainMaybe dmap (_ orElse p.some) + case p => fuccess(p) + } + } + private def findNew: Fu[Option[Puzzle]] = colls .path { diff --git a/modules/puzzle/src/main/Puzzle.scala b/modules/puzzle/src/main/Puzzle.scala index c806e91e1c..4e1555b193 100644 --- a/modules/puzzle/src/main/Puzzle.scala +++ b/modules/puzzle/src/main/Puzzle.scala @@ -29,6 +29,8 @@ case class Puzzle( } err s"Can't apply puzzle $id first move" def color = fen.color.fold[chess.Color](chess.White)(!_) + + def hasTheme(theme: PuzzleTheme) = themes(theme.key) } object Puzzle { diff --git a/modules/relay/src/main/RelaySync.scala b/modules/relay/src/main/RelaySync.scala index f6c28bd2ad..0bfceb5e6b 100644 --- a/modules/relay/src/main/RelaySync.scala +++ b/modules/relay/src/main/RelaySync.scala @@ -155,7 +155,8 @@ final private class RelaySync( (tour.official && chapter.root.mainline.sizeIs > 10) ?? studyApi.analysisRequest( studyId = study.id, chapterId = chapter.id, - userId = study.ownerId + userId = study.ownerId, + unlimited = true ) } >>- { multiboard.invalidate(study.id) diff --git a/modules/relay/src/main/SyncLog.scala b/modules/relay/src/main/SyncLog.scala index e7b0924a3f..c4057236c0 100644 --- a/modules/relay/src/main/SyncLog.scala +++ b/modules/relay/src/main/SyncLog.scala @@ -14,7 +14,10 @@ case class SyncLog(events: Vector[SyncLog.Event]) extends AnyVal { def add(event: SyncLog.Event) = copy( - events = events.take(SyncLog.historySize - 1) :+ event + events = { + if (events.sizeIs > SyncLog.historySize) events drop 1 + else events + } :+ event ) } diff --git a/modules/report/src/main/Reason.scala b/modules/report/src/main/Reason.scala index 7fbb648e3e..0341b8a786 100644 --- a/modules/report/src/main/Reason.scala +++ b/modules/report/src/main/Reason.scala @@ -49,10 +49,10 @@ object Reason { def isGrantedFor(mod: Holder)(reason: Reason) = { import lila.security.Granter reason match { - case Cheat => Granter.is(_.MarkEngine)(mod) - case AltPrint | CheatPrint => Granter.is(_.Admin)(mod) - case Comm => Granter.is(_.Shadowban)(mod) - case Boost | Playbans | Other => Granter.is(_.MarkBooster)(mod) + case Cheat => Granter.is(_.MarkEngine)(mod) + case AltPrint | CheatPrint | Playbans | Other => Granter.is(_.Admin)(mod) + case Comm => Granter.is(_.Shadowban)(mod) + case Boost => Granter.is(_.MarkBooster)(mod) } } } diff --git a/modules/round/src/main/Drawer.scala b/modules/round/src/main/Drawer.scala index 5ff6135c7f..16e00900a9 100644 --- a/modules/round/src/main/Drawer.scala +++ b/modules/round/src/main/Drawer.scala @@ -22,9 +22,9 @@ final private[round] class Drawer( import Pref.PrefZero if (game.playerHasOfferedDrawRecently(pov.color)) fuccess(pov.some) else - pov.player.userId ?? prefApi.getPref map { pref => - pref.autoThreefold == Pref.AutoThreefold.ALWAYS || { - pref.autoThreefold == Pref.AutoThreefold.TIME && + pov.player.userId ?? { uid => prefApi.getPref(uid, _.autoThreefold) } map { autoThreefold => + autoThreefold == Pref.AutoThreefold.ALWAYS || { + autoThreefold == Pref.AutoThreefold.TIME && game.clock ?? { _.remainingTime(pov.color) < Centis.ofSeconds(30) } } || pov.player.userId.exists(isBotSync) } map (_ option pov) @@ -37,7 +37,7 @@ final private[round] class Drawer( case pov if pov.game.history.threefoldRepetition => finisher.other(pov.game, _.Draw, None) case pov if pov.opponent.isOfferingDraw => - finisher.other(pov.game, _.Draw, None, Some(trans.drawOfferAccepted.txt())) + finisher.other(pov.game, _.Draw, None, Messenger.Persistent(trans.drawOfferAccepted.txt()).some) case Pov(g, color) if g playerCanOfferDraw color => val progress = Progress(g) map { _ offerDraw color } messenger.system(g, color.fold(trans.whiteOffersDraw, trans.blackOffersDraw).txt()) diff --git a/modules/round/src/main/Finisher.scala b/modules/round/src/main/Finisher.scala index af35ddbca0..d9f338797c 100644 --- a/modules/round/src/main/Finisher.scala +++ b/modules/round/src/main/Finisher.scala @@ -48,7 +48,7 @@ final private class Finisher( other(game, _.Aborted, none) } else if (game.player(!game.player.color).isOfferingDraw) { - apply(game, _.Draw, None, Some(trans.drawOfferAccepted.txt())) + apply(game, _.Draw, None, Messenger.Persistent(trans.drawOfferAccepted.txt()).some) } else { val winner = Some(!game.player.color) ifFalse game.situation.opponentHasInsufficientMaterial apply(game, _.Outoftime, winner) >>- @@ -62,14 +62,14 @@ final private class Finisher( lila.mon.round.expiration.count.increment() playban.noStart(Pov(game, culprit)) if (game.isMandatory) apply(game, _.NoStart, Some(!culprit.color)) - else apply(game, _.Aborted, None, Some("Game aborted by server")) + else apply(game, _.Aborted, None, Messenger.Persistent("Game aborted by server").some) } def other( game: Game, status: Status.type => Status, winner: Option[Color], - message: Option[String] = None + message: Option[Messenger.SystemMessage] = None )(implicit proxy: GameProxy): Fu[Events] = apply(game, status, winner, message) >>- playban.other(game, status, winner).unit @@ -108,7 +108,7 @@ final private class Finisher( game: Game, makeStatus: Status.type => Status, winnerC: Option[Color], - message: Option[String] = None + message: Option[Messenger.SystemMessage] = None )(implicit proxy: GameProxy): Fu[Events] = { val status = makeStatus(Status) val prog = game.finish(status, winnerC) @@ -139,7 +139,7 @@ final private class Finisher( .flatMap { case (whiteO, blackO) => val finish = FinishGame(g, whiteO, blackO) updateCountAndPerfs(finish) map { ratingDiffs => - message foreach { messenger.system(g, _) } + message foreach { messenger(g, _) } gameRepo game g.id foreach { newGame => newGame foreach proxy.setFinishedGame val newFinish = finish.copy(game = newGame | g) diff --git a/modules/round/src/main/JsonView.scala b/modules/round/src/main/JsonView.scala index 688ab39124..adf47dbd87 100644 --- a/modules/round/src/main/JsonView.scala +++ b/modules/round/src/main/JsonView.scala @@ -7,6 +7,7 @@ import play.api.libs.json._ import scala.math import lila.common.ApiVersion +import lila.common.Json._ import lila.game.JsonView._ import lila.game.{ Pov, Game, Player => GamePlayer } import lila.pref.Pref diff --git a/modules/round/src/main/Messenger.scala b/modules/round/src/main/Messenger.scala index fd7f587dbb..60e11fa5ce 100644 --- a/modules/round/src/main/Messenger.scala +++ b/modules/round/src/main/Messenger.scala @@ -13,6 +13,11 @@ final class Messenger(api: ChatApi) { def volatile(game: Game, message: String): Unit = system(persistent = false)(game, message) + def apply(game: Game, message: Messenger.SystemMessage): Unit = message match { + case Messenger.Persistent(msg) => system(persistent = true)(game, msg) + case Messenger.Volatile(msg) => system(persistent = false)(game, msg) + } + def system(persistent: Boolean)(game: Game, message: String): Unit = if (game.nonAi) { api.userChat.volatile(watcherId(Chat.Id(game.id)), message, _.Round) if (persistent) api.userChat.system(Chat.Id(game.id), message, _.Round) @@ -58,3 +63,10 @@ final class Messenger(api: ChatApi) { private def watcherId(chatId: Chat.Id) = Chat.Id(s"$chatId/w") private def watcherId(gameId: Game.Id) = Chat.Id(s"$gameId/w") } + +private object Messenger { + + sealed trait SystemMessage { val msg: String } + case class Persistent(msg: String) extends SystemMessage + case class Volatile(msg: String) extends SystemMessage +} diff --git a/modules/round/src/main/Rematcher.scala b/modules/round/src/main/Rematcher.scala index 50605f5754..0f36554b99 100644 --- a/modules/round/src/main/Rematcher.scala +++ b/modules/round/src/main/Rematcher.scala @@ -56,10 +56,10 @@ final private class Rematcher( } def no(pov: Pov): Fu[Events] = { - if (isOffering(pov)) messenger.system(pov.game, trans.rematchOfferCanceled.txt()) + if (isOffering(pov)) messenger.volatile(pov.game, trans.rematchOfferCanceled.txt()) else if (isOffering(!pov)) { declined put pov.fullId - messenger.system(pov.game, trans.rematchOfferDeclined.txt()) + messenger.volatile(pov.game, trans.rematchOfferDeclined.txt()) } offers invalidate pov.game.id fuccess(List(Event.RematchOffer(by = none))) @@ -80,7 +80,7 @@ final private class Rematcher( _ = if (pov.game.variant == Chess960 && !chess960.get(pov.gameId)) chess960.put(nextGame.id) _ <- gameRepo insertDenormalized nextGame } yield { - messenger.system(pov.game, trans.rematchOfferAccepted.txt()) + messenger.volatile(pov.game, trans.rematchOfferAccepted.txt()) onStart(nextGame.id) redirectEvents(nextGame) } @@ -88,7 +88,7 @@ final private class Rematcher( } private def rematchCreate(pov: Pov): Events = { - messenger.system(pov.game, trans.rematchOfferSent.txt()) + messenger.volatile(pov.game, trans.rematchOfferSent.txt()) pov.opponent.userId foreach { forId => Bus.publish(lila.hub.actorApi.round.RematchOffer(pov.gameId), s"rematchFor:$forId") } diff --git a/modules/round/src/main/RoundAsyncActor.scala b/modules/round/src/main/RoundAsyncActor.scala index 324c2bd166..f2e36f57b9 100644 --- a/modules/round/src/main/RoundAsyncActor.scala +++ b/modules/round/src/main/RoundAsyncActor.scala @@ -403,7 +403,7 @@ final private[round] class RoundAsyncActor( case WsBoot => handle { game => game.playable ?? { - messenger.system(game, "Lichess has been updated! Sorry for the inconvenience.") + messenger.volatile(game, "Lichess has been updated! Sorry for the inconvenience.") val progress = moretimer.give(game, Color.all, 20 seconds) proxy save progress inject progress.events } diff --git a/modules/round/src/main/Takebacker.scala b/modules/round/src/main/Takebacker.scala index ec4441b96b..26cbf444c9 100644 --- a/modules/round/src/main/Takebacker.scala +++ b/modules/round/src/main/Takebacker.scala @@ -35,7 +35,7 @@ final private class Takebacker( double(game) >>- publishTakeback(pov) dmap (_ -> situation) case Pov(game, color) if (game playerCanProposeTakeback color) && situation.offerable => { - messenger.system(game, trans.takebackPropositionSent.txt()) + messenger.volatile(game, trans.takebackPropositionSent.txt()) val progress = Progress(game) map { g => g.updatePlayer(color, _ proposeTakeback g.turns) } @@ -50,7 +50,7 @@ final private class Takebacker( def no(situation: TakebackSituation)(pov: Pov)(implicit proxy: GameProxy): Fu[(Events, TakebackSituation)] = pov match { case Pov(game, color) if pov.player.isProposingTakeback => - messenger.system(game, trans.takebackPropositionCanceled.txt()) + messenger.volatile(game, trans.takebackPropositionCanceled.txt()) val progress = Progress(game) map { g => g.updatePlayer(color, _.removeTakebackProposition) } @@ -58,7 +58,7 @@ final private class Takebacker( publishTakebackOffer(progress.game) inject List(Event.TakebackOffers(white = false, black = false)) -> situation.decline case Pov(game, color) if pov.opponent.isProposingTakeback => - messenger.system(game, trans.takebackPropositionDeclined.txt()) + messenger.volatile(game, trans.takebackPropositionDeclined.txt()) val progress = Progress(game) map { g => g.updatePlayer(!color, _.removeTakebackProposition) } diff --git a/modules/security/src/main/GarbageCollector.scala b/modules/security/src/main/GarbageCollector.scala index 53a5dbf53e..77cb76123a 100644 --- a/modules/security/src/main/GarbageCollector.scala +++ b/modules/security/src/main/GarbageCollector.scala @@ -97,25 +97,31 @@ final class GarbageCollector( private def isBadAccount(user: User) = user.lameOrTrollOrAlt - private def collect(user: User, email: EmailAddress, msg: => String): Funit = - justOnce(user.id) ?? { - val armed = isArmed() - val wait = (30 + ThreadLocalRandom.nextInt(300)).seconds - val message = - s"Will dispose of @${user.username} in $wait. Email: ${email.value}. $msg${!armed ?? " [SIMULATION]"}" - logger.info(message) - noteApi.lichessWrite(user, s"Garbage collected because of $msg") - irc.garbageCollector(message) >>- { - if (armed) { - doInitialSb(user) - system.scheduler - .scheduleOnce(wait) { - doCollect(user) - } - .unit + private def collect(user: User, email: EmailAddress, msg: => String): Funit = justOnce(user.id) ?? { + hasBeenCollectedBefore(user) flatMap { + case true => funit + case _ => + val armed = isArmed() + val wait = (30 + ThreadLocalRandom.nextInt(300)).seconds + val message = + s"Will dispose of @${user.username} in $wait. Email: ${email.value}. $msg${!armed ?? " [SIMULATION]"}" + logger.info(message) + noteApi.lichessWrite(user, s"Garbage collected because of $msg") + irc.garbageCollector(message) >>- { + if (armed) { + doInitialSb(user) + system.scheduler + .scheduleOnce(wait) { + doCollect(user) + } + .unit + } } - } } + } + + private def hasBeenCollectedBefore(user: User): Fu[Boolean] = + noteApi.byUserForMod(user.id).map(_.exists(_.text startsWith "Garbage collected")) private def doInitialSb(user: User): Unit = Bus.publish( diff --git a/modules/simul/src/main/JsonView.scala b/modules/simul/src/main/JsonView.scala index 9c45b4be6e..f2904d0327 100644 --- a/modules/simul/src/main/JsonView.scala +++ b/modules/simul/src/main/JsonView.scala @@ -3,6 +3,7 @@ package lila.simul import play.api.libs.json._ import lila.common.LightUser +import lila.common.Json._ import lila.game.{ Game, GameRepo } import lila.user.User @@ -12,10 +13,6 @@ final class JsonView( proxyRepo: lila.round.GameProxyRepo )(implicit ec: scala.concurrent.ExecutionContext) { - implicit private val colorWriter: Writes[chess.Color] = Writes { c => - JsString(c.name) - } - implicit private val simulTeamWriter = Json.writes[SimulTeam] private def fetchGames(simul: Simul) = diff --git a/modules/study/src/main/ChapterMaker.scala b/modules/study/src/main/ChapterMaker.scala index d2ca3e969a..248f214af3 100644 --- a/modules/study/src/main/ChapterMaker.scala +++ b/modules/study/src/main/ChapterMaker.scala @@ -19,14 +19,14 @@ final private class ChapterMaker( import ChapterMaker._ - def apply(study: Study, data: Data, order: Int, userId: User.ID): Fu[Chapter] = + def apply(study: Study, data: Data, order: Int, userId: User.ID, withRatings: Boolean): Fu[Chapter] = data.game.??(parseGame) flatMap { case None => data.game ?? pgnFetch.fromUrl flatMap { case Some(pgn) => fromFenOrPgnOrBlank(study, data.copy(pgn = pgn.some), order, userId) case _ => fromFenOrPgnOrBlank(study, data, order, userId) } - case Some(game) => fromGame(study, game, data, order, userId) + case Some(game) => fromGame(study, game, data, order, userId, withRatings) } map { (c: Chapter) => if (c.name.value.isEmpty) c.copy(name = Chapter defaultName order) else c } @@ -125,14 +125,15 @@ final private class ChapterMaker( data: Data, order: Int, userId: User.ID, + withRatings: Boolean, initialFen: Option[FEN] = None ): Fu[Chapter] = for { root <- game2root(game, initialFen) - tags <- pgnDump.tags(game, initialFen, none, withOpening = true, withRating = true) + tags <- pgnDump.tags(game, initialFen, none, withOpening = true, withRatings) name <- { if (data.isDefaultName) - Namer.gameVsText(game, withRatings = false)(lightUser.async) dmap Chapter.Name.apply + Namer.gameVsText(game, withRatings)(lightUser.async) dmap Chapter.Name.apply else fuccess(data.name) } _ = notifyChat(study, game, userId) diff --git a/modules/study/src/main/JsonView.scala b/modules/study/src/main/JsonView.scala index 7494ab5238..d62f78c687 100644 --- a/modules/study/src/main/JsonView.scala +++ b/modules/study/src/main/JsonView.scala @@ -127,21 +127,12 @@ object JsonView { implicit val chapterIdWrites: Writes[Chapter.Id] = stringIsoWriter(Chapter.idIso) implicit val chapterNameWrites: Writes[Chapter.Name] = stringIsoWriter(Chapter.nameIso) - implicit private[study] val uciWrites: Writes[Uci] = Writes[Uci] { u => - JsString(u.uci) - } implicit private val posReader: Reads[Pos] = Reads[Pos] { v => (v.asOpt[String] flatMap Pos.fromKey).fold[JsResult[Pos]](JsError(Nil))(JsSuccess(_)) } implicit private[study] val pathWrites: Writes[Path] = Writes[Path] { p => JsString(p.toString) } - implicit private[study] val colorWriter: Writes[chess.Color] = Writes[chess.Color] { c => - JsString(c.name) - } - implicit private[study] val fenWriter: Writes[FEN] = Writes[FEN] { f => - JsString(f.value) - } implicit private[study] val sriWriter: Writes[Sri] = Writes[Sri] { sri => JsString(sri.value) } diff --git a/modules/study/src/main/ServerEval.scala b/modules/study/src/main/ServerEval.scala index f20f30f3ca..885c07ba39 100644 --- a/modules/study/src/main/ServerEval.scala +++ b/modules/study/src/main/ServerEval.scala @@ -22,14 +22,15 @@ object ServerEval { private val onceEvery = lila.memo.OnceEvery(5 minutes) - def apply(study: Study, chapter: Chapter, userId: User.ID): Funit = + def apply(study: Study, chapter: Chapter, userId: User.ID, unlimited: Boolean = false): Funit = chapter.serverEval.fold(true) { eval => !eval.done && onceEvery(chapter.id.value) } ?? { val unlimitedFu = - fuccess(userId == User.lichessId) >>| userRepo - .byId(userId) - .map(_.exists(Granter(_.Relay))) + fuccess(unlimited) >>| + fuccess(userId == User.lichessId) >>| userRepo + .byId(userId) + .map(_.exists(Granter(_.Relay))) unlimitedFu flatMap { unlimited => chapterRepo.startServerEval(chapter) >>- { fishnet ! StudyChapterRequest( diff --git a/modules/study/src/main/StudyApi.scala b/modules/study/src/main/StudyApi.scala index 9e069100ce..7c5a143722 100644 --- a/modules/study/src/main/StudyApi.scala +++ b/modules/study/src/main/StudyApi.scala @@ -132,7 +132,8 @@ final class StudyApi( addChapter( studyId = study.id, data = data.form.toChapterData, - sticky = study.settings.sticky + sticky = study.settings.sticky, + withRatings )(Who(user.id, Sri(""))) >> byIdWithLastChapter(studyId) case _ => fuccess(none) } orElse importGame(data.copy(form = data.form.copy(asStr = none)), user, withRatings) @@ -587,11 +588,13 @@ final class StudyApi( } } - def addChapter(studyId: Study.Id, data: ChapterMaker.Data, sticky: Boolean)(who: Who): Funit = + def addChapter(studyId: Study.Id, data: ChapterMaker.Data, sticky: Boolean, withRatings: Boolean)( + who: Who + ): Funit = data.manyGames match { case Some(datas) => lila.common.Future.applySequentially(datas) { data => - addChapter(studyId, data, sticky)(who) + addChapter(studyId, data, sticky, withRatings)(who) } case _ => sequenceStudy(studyId) { study => @@ -605,7 +608,7 @@ final class StudyApi( } } >> chapterRepo.nextOrderByStudy(study.id) flatMap { order => - chapterMaker(study, data, order, who.u) flatMap { chapter => + chapterMaker(study, data, order, who.u, withRatings) flatMap { chapter => doAddChapter(study, chapter, sticky, who) } addFailureEffect { case ChapterMaker.ValidationException(error) => @@ -624,9 +627,11 @@ final class StudyApi( studyRepo.updateSomeFields(study) >>- indexStudy(study) } - def importPgns(studyId: Study.Id, datas: List[ChapterMaker.Data], sticky: Boolean)(who: Who) = + def importPgns(studyId: Study.Id, datas: List[ChapterMaker.Data], sticky: Boolean, withRatings: Boolean)( + who: Who + ) = lila.common.Future.applySequentially(datas) { data => - addChapter(studyId, data, sticky)(who) + addChapter(studyId, data, sticky, withRatings)(who) } def doAddChapter(study: Study, chapter: Chapter, sticky: Boolean, who: Who) = @@ -731,7 +736,13 @@ final class StudyApi( chapterRepo.orderedMetadataByStudy(studyId).flatMap { chaps => // deleting the only chapter? Automatically create an empty one if (chaps.sizeIs < 2) { - chapterMaker(study, ChapterMaker.Data(Chapter.Name("Chapter 1")), 1, who.u) flatMap { c => + chapterMaker( + study, + ChapterMaker.Data(Chapter.Name("Chapter 1")), + 1, + who.u, + withRatings = true + ) flatMap { c => doAddChapter(study, c, sticky = true, who) >> doSetChapter(study, c.id, who) } } // deleting the current chapter? Automatically move to another one @@ -841,10 +852,15 @@ final class StudyApi( } } - def analysisRequest(studyId: Study.Id, chapterId: Chapter.Id, userId: User.ID): Funit = + def analysisRequest( + studyId: Study.Id, + chapterId: Chapter.Id, + userId: User.ID, + unlimited: Boolean = false + ): Funit = sequenceStudyWithChapter(studyId, chapterId) { case Study.WithChapter(study, chapter) => Contribute(userId, study) { - serverEvalRequester(study, chapter, userId) + serverEvalRequester(study, chapter, userId, unlimited) } } diff --git a/modules/study/src/main/StudyInvite.scala b/modules/study/src/main/StudyInvite.scala index 7c2a21e313..e67bfc8e34 100644 --- a/modules/study/src/main/StudyInvite.scala +++ b/modules/study/src/main/StudyInvite.scala @@ -40,7 +40,9 @@ final private class StudyInvite( invited <- userRepo .named(invitedUsername) - .map(_.filterNot(_.id == User.lichessId)) orFail "No such invited" + .map( + _.filterNot(_.id == User.lichessId && !Granter(_.StudyAdmin)(inviter)) + ) orFail "No such invited" _ <- study.members.contains(invited) ?? fufail[Unit]("Already a member") relation <- relationApi.fetchRelation(invited.id, byUserId) _ <- relation.has(Block) ?? fufail[Unit]("This user does not want to join") @@ -62,7 +64,6 @@ final private class StudyInvite( else if (inviter.roles has "ROLE_COACH") 20 else if (inviter.hasTitle) 20 else if (inviter.perfs.bestRating >= 2000) 50 - else if (invited.hasTitle) 200 else 100 _ <- shouldNotify ?? notifyRateLimit(inviter.id, rateLimitCost) { val notificationContent = InvitedToStudy( diff --git a/modules/study/src/main/StudyMultiBoard.scala b/modules/study/src/main/StudyMultiBoard.scala index 72b1b1539a..8ca57ea3c3 100644 --- a/modules/study/src/main/StudyMultiBoard.scala +++ b/modules/study/src/main/StudyMultiBoard.scala @@ -106,6 +106,8 @@ final class StudyMultiBoard( private object handlers { + import lila.common.Json._ + implicit val previewPlayerWriter: Writes[ChapterPreview.Player] = Writes[ChapterPreview.Player] { p => Json .obj("name" -> p.name) diff --git a/modules/study/src/main/StudySocket.scala b/modules/study/src/main/StudySocket.scala index 2fe9263b94..554ebf256e 100644 --- a/modules/study/src/main/StudySocket.scala +++ b/modules/study/src/main/StudySocket.scala @@ -124,7 +124,7 @@ final private class StudySocket( case "addChapter" => reading[ChapterMaker.Data](o) { data => val sticky = o.obj("d").flatMap(_.boolean("sticky")) | true - who foreach api.addChapter(studyId, data, sticky = sticky) + who foreach api.addChapter(studyId, data, sticky = sticky, withRatings = true) } case "setChapter" => o.get[Chapter.Id]("d") foreach { chapterId => @@ -304,7 +304,8 @@ final private class StudySocket( "w" -> who ) ) - def setLiking(liking: Study.Liking, who: Who) = notify("liking", Json.obj("l" -> liking, "w" -> who)) + def setLiking(liking: Study.Liking, who: Who) = + notifySri(who.sri, "liking", Json.obj("l" -> liking, "w" -> who)) def setShapes(pos: Position.Ref, shapes: Shapes, who: Who) = version( "shapes", diff --git a/modules/team/src/main/TeamApi.scala b/modules/team/src/main/TeamApi.scala index dfdb861434..0b0c7682ea 100644 --- a/modules/team/src/main/TeamApi.scala +++ b/modules/team/src/main/TeamApi.scala @@ -288,6 +288,7 @@ final class TeamApi( lila.security.Granter(_.ManageTeam)(by) || team.createdBy == by.id || (team.leaders(by.id) && !team.leaders(team.createdBy)) ) { + logger.info(s"toggleEnabled ${team.id}: ${!team.enabled} by @${by.id}") if (team.enabled) teamRepo.disable(team).void >> memberRepo.userIdsByTeam(team.id).map { _ foreach cached.invalidateTeamIds } >> diff --git a/modules/user/src/main/UserRepo.scala b/modules/user/src/main/UserRepo.scala index 060610ed32..b0156c2415 100644 --- a/modules/user/src/main/UserRepo.scala +++ b/modules/user/src/main/UserRepo.scala @@ -173,7 +173,8 @@ final class UserRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionCont coll .update(ordered = false, WriteConcern.Unacknowledged) .one( - $id(userId) ++ (value < 0).??($doc(F.colorIt $gt -3)), + // limit to -3 <= colorIt <= 5 but set when undefined + $id(userId) ++ $doc(F.colorIt -> $not(if (value < 0) $lte(-3) else $gte(5))), $inc(F.colorIt -> value) ) .unit diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 7905ab1361..0e68d44091 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -13,7 +13,7 @@ object Dependencies { val maxmind = "com.sanoma.cda" %% "maxmind-geoip2-scala" % "1.3.1-THIB" val prismic = "io.prismic" %% "scala-kit" % "1.2.19-THIB213" val scaffeine = "com.github.blemale" %% "scaffeine" % "5.1.1" % "compile" - val googleOAuth = "com.google.auth" % "google-auth-library-oauth2-http" % "1.2.2" + val googleOAuth = "com.google.auth" % "google-auth-library-oauth2-http" % "1.3.0" val scalaUri = "io.lemonlabs" %% "scala-uri" % "3.6.0" val scalatags = "com.lihaoyi" %% "scalatags" % "0.10.0" val lettuce = "io.lettuce" % "lettuce-core" % "6.1.5.RELEASE" @@ -21,7 +21,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.13.0" % Test + val specs2 = "org.specs2" %% "specs2-core" % "4.13.1" % Test val apacheText = "org.apache.commons" % "commons-text" % "1.9" val bloomFilter = "com.github.alexandrnikitin" %% "bloom-filter" % "0.13.1" @@ -38,17 +38,17 @@ object Dependencies { val version = "2.4.2" val macros = "com.softwaremill.macwire" %% "macros" % version % "provided" val util = "com.softwaremill.macwire" %% "util" % version % "provided" - val tagging = "com.softwaremill.common" %% "tagging" % "2.3.1" + val tagging = "com.softwaremill.common" %% "tagging" % "2.3.2" def bundle = Seq(macros, util, tagging) } object reactivemongo { - val version = "1.0.7" + val version = "1.0.8" val driver = "org.reactivemongo" %% "reactivemongo" % version val stream = "org.reactivemongo" %% "reactivemongo-akkastream" % version val epoll = "org.reactivemongo" % "reactivemongo-shaded-native" % s"$version-linux-x86-64" - val kamon = "org.reactivemongo" %% "reactivemongo-kamon" % "1.0.7" + val kamon = "org.reactivemongo" %% "reactivemongo-kamon" % "1.0.8" def bundle = Seq(driver, stream) } diff --git a/project/build.properties b/project/build.properties index 10fd9eee04..bb3a9b7dc6 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.5 +sbt.version=1.5.6 diff --git a/project/plugins.sbt b/project/plugins.sbt index 9795a8de6c..7ff9c64724 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,5 @@ resolvers += Resolver.url( url("https://raw.githubusercontent.com/ornicar/lila-maven/master") )(Resolver.ivyStylePatterns) addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.8-lila_1.8") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.5") addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.4.11") diff --git a/public/images/mobile/lichesstv-mobile.png b/public/images/mobile/lichesstv-mobile.png new file mode 100644 index 0000000000..1a47c68f29 Binary files /dev/null and b/public/images/mobile/lichesstv-mobile.png differ diff --git a/public/images/trophy/atomicwc21.png b/public/images/trophy/atomicwc21.png new file mode 100644 index 0000000000..805c4c8184 Binary files /dev/null and b/public/images/trophy/atomicwc21.png differ diff --git a/translation/dest/activity/hi-IN.xml b/translation/dest/activity/hi-IN.xml index 0f749db7cc..f931121801 100644 --- a/translation/dest/activity/hi-IN.xml +++ b/translation/dest/activity/hi-IN.xml @@ -62,6 +62,7 @@ %4$s में %3$s खेल के साथ %1$s (टॉप %2$s%%) रैंक किया गया %4$s में %3$s खेल के साथ %1$s (टॉप %2$s%%) रैंक किया गया + #%1$s स्थान %2$s मे lichess.org पर साइन किया गया %s टीम में शामिल हुए diff --git a/translation/dest/activity/ne-NP.xml b/translation/dest/activity/ne-NP.xml index df052afd29..82d4b3ed15 100644 --- a/translation/dest/activity/ne-NP.xml +++ b/translation/dest/activity/ne-NP.xml @@ -1,7 +1,7 @@ क्रियाकलाप - लाइभ स्ट्रोम आयोजना गर्नुभयो + लाइभ स्ट्रीम आयोजना गर्नुभयो lichess.org लाई %1$s महिनासम्म %2$s भई समर्थन गर्नुभयो lichess.org लाई %1$s महिनासम्म %2$s भई समर्थन गर्नुभयो @@ -62,6 +62,11 @@ %4$s मा %3$s वटा खेल खेली #%1$s औ स्थान (माथिल्लो %2$s%%) %4$s मा %3$s वटा खेलहरु खेली #%1$s औ स्थान (माथिल्लो %2$s%%) + + %sवटा स्वीस् प्रतियोगितामा सहभागी हुनुभयो। + %sवटा स्वीस् प्रतियोगिताहरूमा सहभागी हुनुभयो + + %2$s मा #%1$s स्थान प्राप्त lichess.org मा भर्ना हुनुभयो %s वटा टोलीमा सम्मिलित हुनुभयो diff --git a/translation/dest/activity/or-IN.xml b/translation/dest/activity/or-IN.xml index ab4d2aab72..bf0382980f 100644 --- a/translation/dest/activity/or-IN.xml +++ b/translation/dest/activity/or-IN.xml @@ -1,4 +1,8 @@ କାର୍ଯ୍ୟକଳାପ + + ଜଣେ %2$s ଭାବରେ %1$s ମାସ ପାଇଁ lichess.org କୁ ସମର୍ଥନ କଲେ + ଜଣେ %2$s ଭାବରେ %1$s ମାସ ପାଇଁ lichess.org କୁ ସମର୍ଥନ କଲେ + diff --git a/translation/dest/activity/pl-PL.xml b/translation/dest/activity/pl-PL.xml index 0484ddf9cc..3b555e870f 100644 --- a/translation/dest/activity/pl-PL.xml +++ b/translation/dest/activity/pl-PL.xml @@ -3,8 +3,8 @@ Aktywność Udostępnił stream na żywo - Wsparcie lichess.org przez miesiąc jako %2$s - Wsparcie lichess.org przez %1$s miesiące jako %2$s + Wspomógł lichess.org przez miesiąc jako %2$s + Wspiera Lichess przez %1$s miesiące jako %2$s Wsparcie lichess.org przez %1$s miesięcy jako %2$s Wsparcie lichess.org przez %1$s miesięcy jako %2$s diff --git a/translation/dest/arena/kk-KZ.xml b/translation/dest/arena/kk-KZ.xml index 574deb3344..ed34ba9db7 100644 --- a/translation/dest/arena/kk-KZ.xml +++ b/translation/dest/arena/kk-KZ.xml @@ -41,4 +41,16 @@ Адамдар қосылсын десеңіз, осы URL-мен бөлісіңіз: %s Тепе-теңдік тізбегі: Ойыншының Алаңда қатарынан тепе-теңдіктері болса, алғашқы тепе-теңдік немесе %s жүрістен кейін орын алған тепе-теңдіктер ұпай береді. Тепе-теңдік тізбегін жеңіліс пен тепе-теңдікпен емес, тек жеңіспен ғана тоқтатуға болады. Алаң журналы + + Топты қарау + Барлық %s топты қарау + + Жаңа Топтық Күрес + Басталудың ерекше күні + Сіздің жергілікті уақыт белдеуіңіз. Бұл \"Жарысқа дейінгі уақыт\" баптауына әсер етеді + Берсерк-ті рұқсат ету + Қосымша ұпай жинау үшін ойыншыларға уақытты жартылай қыстартуды рұқсат ету + Ойыншыларға чат бөлмесінде сөйлесуді рұқсат ету + Алаң тізбектері + Қатарынан болған 2 жеңіс 2 ұпайдың орнына 4 ұпай тұрады. diff --git a/translation/dest/arena/or-IN.xml b/translation/dest/arena/or-IN.xml index 3ea04e700d..ce023a84fd 100644 --- a/translation/dest/arena/or-IN.xml +++ b/translation/dest/arena/or-IN.xml @@ -1,2 +1,4 @@ - + + ସ୍କୋରଗୁଡ଼ିକ କିପରି ଗଣନା କରାଯାଏ? + diff --git a/translation/dest/broadcast/he-IL.xml b/translation/dest/broadcast/he-IL.xml index 81318e367a..0fd30c35a8 100644 --- a/translation/dest/broadcast/he-IL.xml +++ b/translation/dest/broadcast/he-IL.xml @@ -1,9 +1,14 @@ שידורים + משדר משחקים חיים + משדר חי חדש + הוסף סבב כרגע בקרוב שהושלמו + שם סבב + מספר סבב שם הטורניר תיאור הטורניר בקצרה תיאור מלא של הטורניר diff --git a/translation/dest/challenge/af-ZA.xml b/translation/dest/challenge/af-ZA.xml index 3ea04e700d..2071078edc 100644 --- a/translation/dest/challenge/af-ZA.xml +++ b/translation/dest/challenge/af-ZA.xml @@ -1,2 +1,25 @@ - + + Uitdagings + Daag uit tot \'n spel + Uitdaging afgewys + Uitdaging aanvaar! + Uitdaging gekanselleer. + Registreer om uitdagings te stuur. + Jy kan nie %s uitdaag nie. + %s aanvaar nie uitdagings nie. + Jou %1$s gradering is te ver van %2$s. + Kan nie uitdaag nie weens voorlopige %s gradering. + %s aanvaar net uitdagings van vriende. + Ek aanvaar nie uitdagings op die oomblik nie. + Dit is \'n ongeleë tyd, vra asseblief weer later. + Die tydskontrole is te vinnig vir my, daag my weer uit met \'n stadiger tydskontrole. + Die tydskontrole is te stadig vir my, daag my weer uit met \'n vinniger tydskontrole. + Ek aanvaar nie uitdagings met hierdie tydskontrole nie. + Stuur eerder vir my \'n gegradeerde uitdaging asseblief. + Stuur eerder vir my \'n vriendskaplike uitdaging asseblief. + Ek aanvaar nie variant-uitdagings op die oomblik nie. + Ek is nie bereid om hierdie variant op die oomblik te speel nie. + Ek aanvaar nie uitdagings deur bots nie. + Ek aanvaar slegs uitdagings deur bots. + diff --git a/translation/dest/challenge/fr-FR.xml b/translation/dest/challenge/fr-FR.xml index f6149aee69..5089227697 100644 --- a/translation/dest/challenge/fr-FR.xml +++ b/translation/dest/challenge/fr-FR.xml @@ -4,7 +4,7 @@ Défier ce joueur Défi refusé Défi accepté! - Défi annulé + Défi annulé. Veuillez vous inscrire pour envoyer des défis. Vous ne pouvez pas défier %s. %s n’accepte pas les défis. diff --git a/translation/dest/challenge/kk-KZ.xml b/translation/dest/challenge/kk-KZ.xml index ca617282e9..3ab08c2a13 100644 --- a/translation/dest/challenge/kk-KZ.xml +++ b/translation/dest/challenge/kk-KZ.xml @@ -17,8 +17,8 @@ Бұл уақыт қалпы мен үшін тым баяу, шапшаңдау ойынға шақыруды жіберіңізші. Мен бұндай уақыт қалпындағы ойындарға шақыруды қабылдамаймын. Оның орнына маған бағаланатын шақыруды жіберіңізші. - Оның орнына маған қалыпты шақыруды жіберіңізші. - Мен қазір шахмат түріне негізделген шақыруларды қабылдамаймын. + Оның орнына маған жай шақыруды жіберіңізші. + Мен қазір классикалық емес шақыруларды қабылдамаймын. Мен қазір осы шахмат түрін ойнағым келмейді. Мен роботтардан шақыруларды қабылдамаймын. Мен тек роботтардан келген шақыруларды қабылдаймын. diff --git a/translation/dest/challenge/ne-NP.xml b/translation/dest/challenge/ne-NP.xml index 3ea04e700d..1879556dfa 100644 --- a/translation/dest/challenge/ne-NP.xml +++ b/translation/dest/challenge/ne-NP.xml @@ -1,2 +1,22 @@ - + + चुनौतीहरू + खेलको लागि चुनौति दिनुहोस् + चुनौती अस्वीकृत + चुनौती स्वीकृत! + चुनौती रद्द। + चुनौति दिनका लागि दर्ता गर्नुहोस्। + तपाईँ %sलाई चुनौति दिन सक्नुहुन्न। + %sले चुनौती स्वीकार गर्नुभएन। + तपाईँको %1$s रेटिङ्ग %2$s भन्दा धेरै टाढा छ। + अस्थायी %s रेटिङका कारण खेलको लागि आमन्त्रण गर्न असम्भव छ। + %sले मित्रहरूको आमन्त्रण मात्र स्वीकार गर्नुहुन्छ। + म यो समय चुनौति स्वीकार गरिरहेको छैँन। + मेरा लागि यो उपयुक्त समय होइन, कृपया केही समय पछि सोध्नुहोला। + यो टाइम कन्ट्रोल मेरा लागि धेरै नै छिटो भयो, कृपया ढिलो खेलका लागि चुनौती दिनुहोस्। + यो टाइम कन्ट्रोल मेरा लागि धेरै नै ढिलो भयो, कृपया छिटो खेलका लागि चुनौती दिनुहोस्। + म यो टाइम कन्ट्रोलको चुनौति स्वीकार गरिरहेको छैँन। + कृपया मलाई सरेटिङ चुनौति पठाउनुहोस्। + कृपया मलाई अरेटिङ चुनौति पठाउनुहोस्। + म बटहरूको आमन्त्रण मात्र स्वीकार गरिरहेको छु। + diff --git a/translation/dest/challenge/ru-RU.xml b/translation/dest/challenge/ru-RU.xml index fc18631961..5428910381 100644 --- a/translation/dest/challenge/ru-RU.xml +++ b/translation/dest/challenge/ru-RU.xml @@ -13,7 +13,7 @@ %s принимает вызовы только от друзей. Я не принимаю вызовы прямо сейчас. Я не принимаю вызовы прямо сейчас, пожалуйста, вызовите меня позже. - Такой контроль времени слишком быстрый для меня, пожалуйста, вызовите меня на игру с контролем времени побольше. + Такой контроль слишком быстрый для меня, пожалуйста, вызовите меня с контролем времени побольше. Такой контроль слишком медленный для меня, пожалуйста, вызовите меня с контролем времени поменьше. Я не принимаю вызовы с таким контролем времени. Вызовите меня на рейтинговую игру, пожалуйста. diff --git a/translation/dest/class/he-IL.xml b/translation/dest/class/he-IL.xml index 5789d8a75b..92adcd4fe8 100644 --- a/translation/dest/class/he-IL.xml +++ b/translation/dest/class/he-IL.xml @@ -71,6 +71,8 @@ כתובת דוא\"ל אמיתית וייחודית של התלמיד. אנו נשלח אליו דוא\"ל לאישור, עם קישור לשחרור החשבון. סגור את החשבון סגור את חשבון התלמיד לצמיתות. + התלמיד לעולם לא יוכל עוד להשתמש בחשבון זה. סגירת החשבון היא סופית. אנא וודא שהתלמיד מבין ומסכים. + אולי תרצה במקום לתת לתלמיד שליטה על החשבון, כך שיוכל להמשיך להשתמש בו. מורים מורה diff --git a/translation/dest/class/or-IN.xml b/translation/dest/class/or-IN.xml index 6cfd607b6d..1ad571dfcc 100644 --- a/translation/dest/class/or-IN.xml +++ b/translation/dest/class/or-IN.xml @@ -1,16 +1,16 @@ - ଛାତ୍ରମାନଙ୍କ ପାଇଁ ଶୀଘ୍ର ସୁରକ୍ଷିତ ଉପଭୋକ୍ତା ନାମ ଏବଂ ପାସ୍ଓ୍ୱାର୍ଡ଼ ସୃଷ୍ଟି କରନ୍ତୁ - ପାସ୍ଓ୍ୱାର୍ଡ଼ ପୁନଃସେଟ୍ କର - ଏବେ ପାସ୍ଓ୍ୱାର୍ଡ଼ କପି କିମ୍ୱା ଲେଖିବାକୁ ନିଶ୍ଚିତ କରନ୍ତୁ। ଆପଣ ଏହାକୁ ପୁନର୍ବାର ଦେଖିବାକୁ ସମର୍ଥ ହେବେ ନାହିଁ! - ପାସ୍ଓ୍ୱାର୍ଡ଼: %s - ଛାତ୍ରଙ୍କ ପାଇଁ ଏକ ନୂତନ ପାସ୍ଓ୍ୱାର୍ଡ଼ ସୃଷ୍ଟି କରନ୍ତୁ + ଛାତ୍ରମାନଙ୍କ ପାଇଁ ଶୀଘ୍ର ସୁରକ୍ଷିତ ଉପଭୋକ୍ତା ନାମ ଏବଂ ପାସ୍‍ୱାର୍ଡ଼ ସୃଷ୍ଟି କରନ୍ତୁ + ପାସ୍‍ୱାର୍ଡ଼ ପୁନଃସେଟ୍ କର + ଏବେ ପାସ୍‍ୱାର୍ଡ଼ କପି କିମ୍ୱା ଲେଖିବାକୁ ନିଶ୍ଚିତ କରନ୍ତୁ। ଆପଣ ଏହାକୁ ପୁନର୍ବାର ଦେଖିବାକୁ ସମର୍ଥ ହେବେ ନାହିଁ! + ପାସ୍‍ୱାର୍ଡ଼: %s + ଛାତ୍ରଙ୍କ ପାଇଁ ଏକ ନୂତନ ପାସ୍‍ୱାର୍ଡ଼ ସୃଷ୍ଟି କରନ୍ତୁ ଛାତ୍ର: %1$s ଉପଭୋକ୍ତା ନାମ: %2$s -ପାସ୍ଓ୍ୱାର୍ଡ଼: %3$s - କୌଣସି ଇମେଲ୍ ଠିକଣା ଆବଶ୍ୟକ ନାହିଁ। ଏକ ପାସ୍ଓ୍ୱାର୍ଡ଼ ସୃଷ୍ଟି ହେବ, ଏବଂ ଆପଣଙ୍କୁ ଏହାକୁ ଛାତ୍ରଙ୍କ ନିକଟକୁ ପଠାଇବାକୁ ପଡ଼ିବ, ଯାହା ଫଳରେ ସେମାନେ ଲଗ୍ ଇନ୍ କରିପାରିବେ। +ପାସ୍‍ୱାର୍ଡ଼: %3$s + କୌଣସି ଇମେଲ୍ ଠିକଣା ଆବଶ୍ୟକ ନାହିଁ। ଏକ ପାସ୍‍ୱାର୍ଡ଼ ସୃଷ୍ଟି ହେବ, ଏବଂ ଆପଣଙ୍କୁ ଏହାକୁ ଛାତ୍ରଙ୍କ ନିକଟକୁ ପଠାଇବାକୁ ପଡ଼ିବ, ଯାହା ଫଳରେ ସେମାନେ ଲଗ୍ ଇନ୍ କରିପାରିବେ। ପରିଚାଳିତ ଏହି ଛାତ୍ର ଆକାଉଣ୍ଟ୍ ଟି ପରିଚାଳିତ ଅଟେ ପରିଚାଳିତରୁ ସ୍ୱୟଂଶାସିତକୁ ଅପଗ୍ରେଡ୍ କର - ଏକ ସ୍ନାତକ ଆକାଉଣ୍ଟ ପୁନର୍ବାର ପରିଚାଳନା କରାଯାଇପାରିବ ନାହିଁ। ଛାତ୍ର ଶିଶୁ ମୋଡ୍ ଟୋଗଲ୍ କରିବାକୁ ଏବଂ ନିଜେ ପାସ୍ଓ୍ୱାର୍ଡ଼ ପୁନଃସେଟ୍ କରିବାକୁ ସମର୍ଥ ହେବେ। + ଏକ ସ୍ନାତକ ଆକାଉଣ୍ଟ ପୁନର୍ବାର ପରିଚାଳନା କରାଯାଇପାରିବ ନାହିଁ। ଛାତ୍ର ଶିଶୁ ମୋଡ୍ ଟୋଗଲ୍ କରିବାକୁ ଏବଂ ନିଜେ ପାସ୍‍ୱାର୍ଡ଼ ପୁନଃସେଟ୍ କରିବାକୁ ସମର୍ଥ ହେବେ। diff --git a/translation/dest/contact/he-IL.xml b/translation/dest/contact/he-IL.xml index 44ea6963ab..d17b7182c1 100644 --- a/translation/dest/contact/he-IL.xml +++ b/translation/dest/contact/he-IL.xml @@ -27,6 +27,7 @@ אני רוצה לנקות את ההיסטוריה או הדירוג שלי לא ניתן לנקות את היסטוריית המשחק, היסטוריית הפאזלים, או הדירוגים שלך. אני רוצה לדווח על שחקן + כדי לדווח על שחקן, השתמש בטופס הדיווח אתה יכול גם להגיע לדף זה על ידי לחיצה על כפתור הדוח %s בדף הפרופיל. אל תדווחו על שחקנים בפורום. אל תשלחו לנו אימיילים כדי לדווח. @@ -51,6 +52,7 @@ אפשרי לבצע מט רק אם פרש או רץ, אם ליריב יש יותר ממלך על הלוח. נקודות מד כושר לא הוענקו בדוק אם שיחקת משחק מדורג. משחק לא מדורג לא משפיע על מד הכושר. + בנסיבות מסויימות כאשר משחק משוחק אל מול חשבון של בוט, משחק מנוקד עשוי שלא להעניק נקודות אם יקבע שהשחקן מנצל את הבוט לצבירת ניקוד. שגיאה בדף אם התמודדת עם שגיאה בדף, אתה יכול לדווח על זה: ערער על איסור או על הגבלה בIP diff --git a/translation/dest/contact/kk-KZ.xml b/translation/dest/contact/kk-KZ.xml index e6b60417ad..7664061d15 100644 --- a/translation/dest/contact/kk-KZ.xml +++ b/translation/dest/contact/kk-KZ.xml @@ -51,7 +51,7 @@ FIDE Шахмат Заңының §6.9 сәйкес, егер ережеге сай кез-келген жүрістермен матқа жету мүмкінкіндігі бар болса, онда ойын қорытындысы тепе-тең деп саналмайды Егер қарсыласта патшадан басқа тағы тас болса, оған матты жалғыз ат немесе пілмен ғана қоюға болады. Ешбір рейтинг ұпайы қосылмады - Бағаланатын ойын екенін тексеріңіз. Қалыпты ойындар ойыншының рейтингіне әсер етпейді. + Бағаланатын ойын екенін тексеріңіз. Жай ойындар ойыншының рейтингіне әсер етпейді. Ақаулық парақшасы Ақаулық парақшасына тап болсаңыз, осында хабарлауыңызға болады: Шектеуге не IP шектеуге қарсылық шағымы diff --git a/translation/dest/contact/or-IN.xml b/translation/dest/contact/or-IN.xml index 0eb3c534d2..de64c50c1a 100644 --- a/translation/dest/contact/or-IN.xml +++ b/translation/dest/contact/or-IN.xml @@ -1,7 +1,8 @@ - ମୁଁ ମୋର ପାସ୍ଓ୍ୱାର୍ଡ଼ ଭୁଲିଗଲି - ଆପଣଙ୍କ ଦ୍ୱିତୀୟ ଫ୍ୟାକ୍ଟର ଅପସାରଣ କରିବାକୁ ଏକ ପାସ୍ଓ୍ୱାର୍ଡ଼ ପୁନଃସେଟ୍ ସମ୍ପୂର୍ଣ୍ଣ କରନ୍ତୁ + ମୁଁ ମୋର ପାସ୍‍ୱାର୍ଡ଼ ଭୁଲିଗଲି + ଆପଣଙ୍କ ଦ୍ୱିତୀୟ ଫ୍ୟାକ୍ଟର ଅପସାରଣ କରିବାକୁ ଏକ ପାସ୍‍ୱାର୍ଡ଼ ପୁନଃସେଟ୍ ସମ୍ପୂର୍ଣ୍ଣ କରନ୍ତୁ ଏକ ଖାତା ବନ୍ଦ କରିବାକୁ ଆମକୁ ଇମେଲ୍ ମାଧ୍ୟମରେ କୁହନ୍ତୁ ନାହିଁ, ଆମେ ତାହା କରିବୁ ନାହିଁ। ଏକ ଖାତା ପୁନଃଖୋଲିବାକୁ ଆମକୁ ଇମେଲ୍ ମାଧ୍ୟମରେ କୁହନ୍ତୁ ନାହିଁ, ଆମେ ତାହା କରିବୁ ନାହିଁ। + ଫୋରମର Lichess Feedback ବିଭାଗରେ diff --git a/translation/dest/coordinates/be-BY.xml b/translation/dest/coordinates/be-BY.xml index d6121f2f79..0983f6560b 100644 --- a/translation/dest/coordinates/be-BY.xml +++ b/translation/dest/coordinates/be-BY.xml @@ -2,8 +2,8 @@ Каардынаты Трэніроўка па каардынатах - Сярэдні лік за белых: %s - Сярэдні лік за чорных: %s + Сярэдні вынік за белых: %s + Сярэдні вынік за чорных: %s Ведаць каардынаты шахматнай дошкі - вельмі важны шахматны навык: Большасць шахматных задач та заданняў выкарыстоўваць шахматнаю натацыю. Гэта спрошчвае гутарку з сябрамі-шахматыстамі, бо вы абодва разумееце \"шахматную мову\". diff --git a/translation/dest/coordinates/ne-NP.xml b/translation/dest/coordinates/ne-NP.xml index 3fcc435995..ca996ecd45 100644 --- a/translation/dest/coordinates/ne-NP.xml +++ b/translation/dest/coordinates/ne-NP.xml @@ -1,12 +1,12 @@ - को‍-अर्डिनेटहरु - को-अर्डिनेटको अभ्यास + समकक्षहरु + समकक्षको अभ्यास सेतो लिएर औसत अंक: %s कालो लिएर औसत अंक: %s - चेस बोर्ड को को-अर्डिनेटहरूको को ज्ञान हुनु धेरै महत्वपूर्ण छ: - प्राय सबै चेस कोर्स र अभ्यासहरुमा एल्जेब्रिक नोटेशन प्रयोग हुन्छ। - यसले तपाइलाई आफ्नो चेस खेल्ने मित्रहरु संग कुरा गर्न सहज हुन्छ, किनकि तपाईहरु दुवै \"चेस को भाषा\" बुझ्नुहुन्छ। + चेस बोर्डका समकक्षहरूको को ज्ञान हुनु धेरै महत्वपूर्ण छ: + प्राय सबै चेस कोर्स र अभ्यासहरूमा विजीय सङ्केत प्रयोग हुन्छ। + यसले तपाइलाईँ आफ्ना चेस खेल्ने मित्रहरू संग कुरा गर्न सहज हुन्छ, किनकि तपाईहरू दुवै \"चेस को भाषा\" बुझ्नुहुन्छ। तपाइले खेलहरु प्रभावकारी रुपमा विश्लेषण गर्न सक्नुहुन्छ किनकि तपाइलाई कोठाको नामहरु खोजिरहनु पर्दैन। कोठाको नाम बोर्डमा आउछ अनि तपाइले सहि कोठामा क्लिक गर्नुपर्ने हुन्छ| तपाइसंग तपाइले सकेजति कोठा चिन्न ३० सेकेन्ड हुनेछ! अभ्यास सुरु गर्नुहोस् diff --git a/translation/dest/coordinates/or-IN.xml b/translation/dest/coordinates/or-IN.xml index 3ea04e700d..88cdffeb23 100644 --- a/translation/dest/coordinates/or-IN.xml +++ b/translation/dest/coordinates/or-IN.xml @@ -1,2 +1,5 @@ - + + ଧଳା ଭାବରେ ହାରାହାରି ସ୍କୋର୍: %s + କଳା ଭାବରେ ହାରାହାରି ସ୍କୋର୍: %s + diff --git a/translation/dest/emails/aa-ER.xml b/translation/dest/emails/aa-ER.xml index 3ea04e700d..a79101e711 100644 --- a/translation/dest/emails/aa-ER.xml +++ b/translation/dest/emails/aa-ER.xml @@ -1,2 +1,6 @@ - + + Bevestig jou lichess.org-rekening, %s + Klik op die skakel om jou Lichess-rekening te aktiveer. + As jy nie by Lichess geregistreer het nie, kan jy hierdie boodskap veilig ignoreer. + diff --git a/translation/dest/emails/az-AZ.xml b/translation/dest/emails/az-AZ.xml index fa7366b9d0..e594603ee5 100644 --- a/translation/dest/emails/az-AZ.xml +++ b/translation/dest/emails/az-AZ.xml @@ -5,11 +5,11 @@ \"Lichess\"-də qeydiyyatdan keçməmisinizsə, bu mesaja əhəmiyyət verməyə bilərsiniz. Lichess.org şifrənizi sıfırlayın, %s Hesabınızın şifrəsini sıfırlamaq üçün bir sorğu aldıq. - Əgər bu sorğunu siz etmisinizsə, aşağıdakı linkə klikləyin. Əks halda, bu e-poçta əhəmiyyət verməyin. + Əgər bu sorğunu siz etmisinizsə, aşağıdakı linkə klikləyin. Siz etməmisinizsə, bu e-poçta əhəmiyyət verməyin. Yeni e-poçt ünvanınızı təsdiqləyin, %s E-poçt ünvanınızın dəyişdirilməsi üçün sorğu göndərdiniz. Bu e-poçt sizin üçün əlçatandırsa, zəhmət olmasa aşağıdakı bağlantıya klikləyin: - \"Lichess.org\"-a xoş gəldiniz, %s + Lichess.org\'a xoş gəldiniz, %s Hesabınız https://lichess.org üzərindən uğurla yaradıldı. Profil səhifəniz: %1$s. Profilinizi %2$s üzərindən özəlləşdirə bilərsiniz. diff --git a/translation/dest/emails/be-BY.xml b/translation/dest/emails/be-BY.xml index 7a5255109c..b8de0546e2 100644 --- a/translation/dest/emails/be-BY.xml +++ b/translation/dest/emails/be-BY.xml @@ -2,21 +2,21 @@ %s, пацвердзіце ваш уліковы запіс на lichess.org Націсніце на спасылку, каб актываваць уліковы запіс Lichess: - Калі вы не рэгістраваліся на Lichess, проста праігнаруйце гэты ліст. - %s, вы скідваеце ваш пароль на lichess.org - Мы атрымалі запыт на скасаванне пароля да вашага ўліковага запісу. + Калі вы не рэгістраваліся на Lichess, проста праігнаруйце гэта паведамленне. + %s, вы скідаеце ваш пароль на lichess.org + Мы атрымалі запыт на скіданне пароля да вашага ўліковага запісу. Калі запыт выкананы вамі, націсніце на спасылку ніжэй. Калі не, праігнаруйце гэты ліст. %s, пацвердзіце новы адрас пошты Мы атрымалі запыт на змену вашага адраса электроннай пошты. - Доўжачы пацверджанні наяўнасці доступу да гэтай пошты, калі ласка, націсніце на спасылку ніжэй: + Для пацвярджэння таго, што ў вас ёсць доступ да адраса электроннай пошты, калі ласка, націсніце спасылку ніжэй: Вітаем вас на lichess.org, %s - Вы паспяхова зарэгістравалі свой акаўнт на https://lichess.org. + Вы паспяхова зарэгістравалі свой уліковы запіс на https://lichess.org. -Вось спасылка на Ваш профіль: %1$s. Вы можаце наладзіць яго тут %2$s. +Вось спасылка на ваш профіль: %1$s. Вы можаце наладзіць яго тут %2$s. -Поспехаў, і ды знойдуць Вашы фігуры шлях да караля суперніка! +Поспехаў, і ды знойдуць Вашы фігуры шлях да караля саперніка! Уваход на lichess.org, %s - (Клік па спасылцы не працуе? Паспрабуйце ўставіць у ваш браўзер!) - Гэта службовы ліст ад %s. + (Націск на спасылку не працуе? Паспрабуйце ўставіць у ваш браўзер!) + Гэта службовы ліст, які звязаны з вашым выкарыстаннем %s. Каб звязацца з намі, калі ласка, выкарыстоўвайце %s. diff --git a/translation/dest/emails/kk-KZ.xml b/translation/dest/emails/kk-KZ.xml index 6485c264e4..739a87cd5a 100644 --- a/translation/dest/emails/kk-KZ.xml +++ b/translation/dest/emails/kk-KZ.xml @@ -6,7 +6,7 @@ %s, lichess.org құпиясөзін қайта орнатыңыз Бізге тіркелгіңіздің кұпиясөзін қайта орнату туралы тапсырыс келді. Егер бұл тапсырысты жіберген сіз болсаңыз, төмендегі сілтемеге басыңыз. Кері жағдайда осы хатты назарсыз қалдыра салыңыз. - %s, жаңа поштаңызды растаңыз + Жаңа поштаңызды растаңыз Сіз поштаңызды ауыстыруға тапсырыс жіберген едіңіз. Сол поштаға кіруге рұқсатыңыз бар екенін растау үшін төмендегі сілтемеге басыңыз: %s, Личес-ке қош келдіңіз diff --git a/translation/dest/emails/ky-KG.xml b/translation/dest/emails/ky-KG.xml index 3ea04e700d..52e9f564fe 100644 --- a/translation/dest/emails/ky-KG.xml +++ b/translation/dest/emails/ky-KG.xml @@ -1,2 +1,4 @@ - + + (Башсаныз иштебей жатыр бы? Анда башка браузер мен кириниз) + diff --git a/translation/dest/emails/la-LA.xml b/translation/dest/emails/la-LA.xml index fe55e1df2e..70d9dc2055 100644 --- a/translation/dest/emails/la-LA.xml +++ b/translation/dest/emails/la-LA.xml @@ -1,5 +1,5 @@ Signum vestrum lichess.org repone, %s - Salve lichess.org, %s + Bene advenisti ad lichess.org, %s diff --git a/translation/dest/emails/ne-NP.xml b/translation/dest/emails/ne-NP.xml index bbdaf293b2..08609051a8 100644 --- a/translation/dest/emails/ne-NP.xml +++ b/translation/dest/emails/ne-NP.xml @@ -1,21 +1,21 @@ - %s, कृपया तपाइको lichess.org खाता पुष्टी गर्नुहोस्। + %s, कृपया तपाईँको lichess.org खाता पुष्टी गर्नुहोस्। तपाईंको Lichess खाता सुचारु गर्न यो लिंकमा क्लिक गर्नुहोस्: - यदि तपाईले Lichessमा दर्ता गर्नुभएको छैन भने यो सन्देशलाई नडराइकन बेवास्ता गर्नुहोस्। + यदि तपाईँले Lichessमा दर्ता गर्नुभएको छैन भने यो सन्देशलाई बेवास्ता गर्नुहोस्। %s, तपाईंको lichess.org को पासवर्ड परिवर्तन गर्नुहोस्। हामीले तपाईंको खाताको पासवर्ड फेर्ने अनुरोध प्राप्त गरेका छौं। - यदि तपाईले यो अनुरोध गर्नुभएको हो भने तलको लिंकमा क्लिक गर्नुहोस्। अन्यथा यो इमेललाइ बेवास्ता गर्नुहोस्। - %s, नयाँ इमेल पुष्टी गर्नुहोस्। - तपाईले इमेल ठेगाना परिवर्तन गर्न अनुरोध गर्नुभएको छ। + यदि तपाईँले यो अनुरोध गर्नुभएको हो भने तलको लिङ्कमा क्लिक गर्नुहोस्। अन्यथा यो इमेललाई बेवास्ता गर्नुहोस्। + %s, नयाँ इमेल पुष्टि गर्नुहोस्। + तपाईँले इमेल परिवर्तन गर्न अनुरोध गर्नुभएको छ। यो इमेलमा तपाईंको पहुँच छ भन्ने यकिन गर्न तलको लिंकमा क्लिक गर्नुहोस्: %s, lichess.org मा तपाईलाई स्वागत् छ। - तपाई https://lichess.org मा खाता दर्ता गर्न सफल हुनुभएको छ। + तपाईँ https://lichess.org मा खाता दर्ता गर्न सफल हुनुभएको छ। -तपाईंको प्रोफाइल पृष्ठ यहाँ छ: %1$s। यसलाई %2$s मा तपाईलाई अनुकुल पर्नेगरी परिवर्तन गर्न सक्नुहुनेछ। +तपाईंको प्रोफाइल पृष्ठ यहाँ छ: %1$s। यसलाई %2$s मा तपाईँलाई अनुकुल पर्नेगरी परिवर्तन गर्न सक्नुहुनेछ। -रमाइलो गर्नुहोस्। तपाईंको गोटीहरु विपक्षी राजा सम्म पुगुन्। - %s, lichess.org मा लग इन गरौं +रमाइलो गर्नुहोस्। तपाईंका गोटीहरू विपक्षी राजा सम्म पुगुन्। + %s, lichess.org मा लग इन गर्नुहोस्। (क्लिक भएन? ब्राउजरमा पेष्ट गरेर हेर्नुहोस्!) यो तपाईंको %s को प्रयोग संग सम्बन्धित इमेल हो। कृपया हामीसँग सम्पर्क गर्न %s को प्रयोग गर्नुहोस्। diff --git a/translation/dest/emails/or-IN.xml b/translation/dest/emails/or-IN.xml index 7c3d8ec4c9..fcfc30b490 100644 --- a/translation/dest/emails/or-IN.xml +++ b/translation/dest/emails/or-IN.xml @@ -1,7 +1,7 @@ ଆପଣଙ୍କର lichess.org ଖାତା,%s ନିଶ୍ଚିତ କରନ୍ତୁ | - ଆପଣଙ୍କ lichess.org ପାସ୍ଓ୍ୱାର୍ଡ଼ କୁ ପୁନଃସେଟ୍ କରନ୍ତୁ, %s - ଆପଣଙ୍କ ଆକାଉଣ୍ଟ ପାଇଁ ପାସ୍ଓ୍ୱାର୍ଡ଼ ପୁନଃସେଟ୍ କରିବାକୁ ଆମେ ଏକ ଅନୁରୋଧ ପାଇଲୁ। + ଆପଣଙ୍କ lichess.org ପାସ୍‍ୱାର୍ଡ଼ କୁ ପୁନଃସେଟ୍ କରନ୍ତୁ, %s + ଆପଣଙ୍କ ଆକାଉଣ୍ଟ ପାଇଁ ପାସ୍‍ୱାର୍ଡ଼ ପୁନଃସେଟ୍ କରିବାକୁ ଆମେ ଏକ ଅନୁରୋଧ ପାଇଲୁ। ଯଦି ଆପଣ ଏହି ଅନୁରୋଧ କରିଛନ୍ତି, ନିମ୍ନ ଲିଙ୍କ୍ କ୍ଲିକ୍ କରନ୍ତୁ। ଯଦି ନୁହେଁ, ତେବେ ଆପଣ ଏହି ଇମେଲ୍ ଅଣଦେଖା କରିପାରିବେ। diff --git a/translation/dest/emails/os-SE.xml b/translation/dest/emails/os-SE.xml index 7d21d92042..84607506b1 100644 --- a/translation/dest/emails/os-SE.xml +++ b/translation/dest/emails/os-SE.xml @@ -1,22 +1,22 @@ Сфидар кæн дæ lichess.org аккаунт, %s - Дæ Lichess аккаунт цæмæй скусын кæнай, æрæлхъив дæнцæгыл: + Дæ Lichess аккаунт цæмæй скусын кæнай, нылхъив дæнцæгыл: Дæхи Lichess-ы кæд регистраци нæ кодтай, уæд ацы фыстæг ницæмæ дар. Раппар дæ lichess.org пароль, %s - Мах райстам бафæрст дæ аккаунты паролы раппæрыныл. - Ды кæд уыцы бафæрст скодтай, уæд æрæлхъив уыцы дæнцæгыл. Кæд нæ, уæд ницæмæ дар уыцы фыстаг. - Сфидар кæн дæ ног пост, %s - Ды бафарстай дæ посты ивынад. - Цæмæй дæ пост сфидар кæнай, æрæлхъив уыцы дæнцæгыл: + Мах райстам бафарст дæ аккаунты пароль раппарын. + Ды кæд ахæм бафарст кодтай, уæд нылхъив дæнцæгыл дæлдæр. Кæд нæ, уæд ницæмæ дар ацы фыстаг. + Сфидар кæн дæ ног посты адрис, %s + Ды бафарстай дæ посты адрис баивын. + Цæмæй дæ посты адрис сфидар кæнай, нылхъив дæнцæгыл дæлдæр: Æгас цу lichess.org-мæ, %s Æнтыстджынæй дæ lichess.org аккаунт сарæзтай. -Мæнæ ам ис дæ профилы фарс: %1$s. Сфæлындын æй дæ бон у %2$s. +Мæнæ ам ис дæ профилы фарс: %1$s. Сфæлындын æй дæ бон у ам: %2$s. -Хъæлдзаг у, æмæ дæ фигурæтæ ныхмæлæууæджы къаролмæ цæмæй сæ фæндаг æдзухдæр ссарой! +Бантысæд дын, цæмæй дæ фигурæтæ ныхмæлæууæджы къаролмæ сæ фæндаг æдзухдæр ссарой! Бацу lichess.org-мæ, %s (Æлхъивын нæ кусы? Бафæлвар æй дæ браузермæ бавæрын!) - Уый у сервисы фыстæг, дæ %s спайдакæнынмæ баст кæй у, ахæм. - Махимæ цæмæй хи сбæттын, %s пайда кæн. + Ай у службæйон фыстæг, дæ %s спайдакæнынмæ баст кæй у, ахæм. + Махимæ цæмæй бастдзинад скæнын, %s пайда кæн. diff --git a/translation/dest/emails/sa-IN.xml b/translation/dest/emails/sa-IN.xml index 3ea04e700d..5a0f646cdf 100644 --- a/translation/dest/emails/sa-IN.xml +++ b/translation/dest/emails/sa-IN.xml @@ -1,2 +1,4 @@ - + + क्रीडकस्य नामः %s + diff --git a/translation/dest/emails/te-IN.xml b/translation/dest/emails/te-IN.xml index 1d29f0a6f8..032e47a95e 100644 --- a/translation/dest/emails/te-IN.xml +++ b/translation/dest/emails/te-IN.xml @@ -1,7 +1,7 @@ మీ lichess.org ఖాతా %s, ధ్రువపరచండి - మీ Lichess ఖాతాను నిద్రలేపి, వాడడం మొదలుపెట్టడానికి ఈ లంకెను నొక్కండి: + మీ Lichess ఖాతాను వాడడం మొదలుపెట్టడానికి ఈ లంకెను నొక్కండి: ఒకవేళ మీరు lichess లో చేరియుండక పొతే ఈ సందేశాన్ని పట్టించుకోనవసరం లేదు. మీ lichess రహస్యపదాన్ని మార్చండి, %s మీ ఖాతాకు ప్రస్తుతమున్న రహస్యపదాన్ని తుడిచిపెట్టి, కొత్త రహస్యపదాన్ని సృష్టించమని అభ్యర్థన వచ్చింది. diff --git a/translation/dest/faq/fa-IR.xml b/translation/dest/faq/fa-IR.xml index 110b0f053a..9e4a89085b 100644 --- a/translation/dest/faq/fa-IR.xml +++ b/translation/dest/faq/fa-IR.xml @@ -15,6 +15,7 @@ بازی منصفانه اگر حریف شما به صورت مداوم بازی را ترک کند، از بازی محروم می شود این به این معنی است که به صورت موقت اجازه بازی کردن را نخواهد داشت. این امر در حساب کاربری فرد نمایش داده نمی شود. اگر این رفتار ادامه پیدا کند، مدت زمان محرومیت از بازی افزایش می یابد و انجام این کار در مدت زمان طولانی ممکن است منجر به مسدود شدن حساب کاربر شود. چگونه می‌توانم یک مدیر باشم؟ + گیم پلی معرفی و مقدمه خوب موقعیت‌ها پیکربندی نمایید @@ -26,5 +27,9 @@ نام کاربری من چه می‌تواند باشد؟ دستورالعمل‌ها آیا می‌توانم نام کاربری خود را تغییر دهم؟ + بازیکنان با قدرت یکسان + چگونه وقتی بازی می کنیم ریتینگ ها را پنهان کنیم؟ + تنظیمات نمایش + من یک بازی را بخاطر قطعی اینترنت باختم. آیا می توانم ریتینگ از دست رفته ام را بازگردانی کنم؟ چگونه... diff --git a/translation/dest/faq/kk-KZ.xml b/translation/dest/faq/kk-KZ.xml index 89406270b0..3ecfca3adb 100644 --- a/translation/dest/faq/kk-KZ.xml +++ b/translation/dest/faq/kk-KZ.xml @@ -33,9 +33,9 @@ (бастапқы уақыт) + 40 × (қосылғыш) < %1$sс = %2$s ≥ %1$s с = %2$s - Личесте қандай шахмат түрлерін ойнасам болады? - Личесте әдеттегі шахмат пен %1$s бар. - 8 шахмат түрлері + Личесте шахматтың қай түрлерін ойнасам болады? + Личесте классикалық әрі %1$s бар. + шахматтың 8 түрі Қателіктердің орташа мәні (ACPL – average centipawn loss) деген не? Қателіктердің орташа мәнінің бірлігі сентипоун (\"centi\" - жүзден бір бөлік, \"pawn\" - сарбаз). Бұл шама ойыншының жетістік дәрежесін көрсетеді. Ойын кезінде бұл шама соншалықты маңызды емес, бірақ компьютер үшін ойын күйін бағалауда орны зор. @@ -115,7 +115,7 @@ Мықтылар қатары белгіленген рейтингте кем дегенде 30 бағаланатын ойын ойнау, кейінгі аптада белгіленген рейтингте бағаланатын ойынға қатысу, - рейтинг ауытқуы стандарт шахматта %1$s-тен кем, басқа түрлерінде %2$s-тен кем болу керек, + рейтинг ауытқуы классикалық шахматта %1$s-тен кем, басқа түрлерінде %2$s-тен кем болу керек, белгіленген рейтингтің үздік 10 қатарында болу. Екінші талап – өз тіркелгілерін пайдаланбайтын ойыншылар Мықтылар қатарынан шығады. Рейтинг басқа FIDE, USCF пен ICC сияқты ұйымдармен салыстырғанда неге жоғарылау болып тұр? @@ -123,7 +123,7 @@ Рейтингті салыстырмалы шама деп ойлаған дұрыс: ойыншылар арасында рейтинг айырмашылығына қарап, кім жеңетінін, тең ойнайтынын не жеңілетінін бағалауға болады. \"Менің рейтингім осынша\" дегеннің ешбір мәні жоқ, тек басқа ойыншымен салыстырғаннан пайдасы болар. Рейтингті ойын кезінде қалай жасырсам болады? - %1$s-ге кіріп Zen қалпын қосыңыз немесе ойын кезінде %2$s басыңыз. + %1$s-ге кіріп Оқшау көріністі қосыңыз немесе ойын кезінде %2$s басыңыз. баптауларды көрсету Желінің ажырауы/кідірісі кесірінен ойын тоқталды. Рейтинг ұпайларын қайтара аласыз ба? Өкінішке орай, бұл ажырау не кідіріс кімнің жағынан болғанына қарамастан, біз осы жайтта ұпайды қайтара алмаймыз. Біздің жағымыздан туындаған ажырау мен кідіріс – мүлдем сирек болатын жағдай. Онымен қатар, Личес жүйесінің қайта қосылуынан сіз уақыт жоғалтсаңыз, әділсіз шығын болмағандай біз ойынды үзіп тастаймыз. diff --git a/translation/dest/faq/or-IN.xml b/translation/dest/faq/or-IN.xml index acbf89c93d..03505f7fde 100644 --- a/translation/dest/faq/or-IN.xml +++ b/translation/dest/faq/or-IN.xml @@ -1,5 +1,5 @@ - ହଁ। Lichess ପ୍ରକୃତରେ ଅନ୍ୟ ଖୋଲା-ଉତ୍ସ ସାଇଟଗୁଡ଼ିକୁ ପ୍ରେରଣା ଦେଇଛି ଯାହା ଆମର %1$s, %2$s, କିମ୍ୱା %3$s ବ୍ୟବହାର କରନ୍ତି। + ହଁ। Lichess ପ୍ରକୃତରେ ଅନ୍ୟ ଖୋଲା-ଉତ୍ସ ସାଇଟଗୁଡ଼ିକୁ ପ୍ରେରଣା ଦେଇଛି ଯାହା ଆମର %1$s, %2$s, କିମ୍ୱା %3$s ବ୍ୟବହାର କରନ୍ତି। ପ୍ରଦର୍ଶନ ଅଗ୍ରାଧିକାର diff --git a/translation/dest/faq/os-SE.xml b/translation/dest/faq/os-SE.xml index 1d45be186d..4a8932c291 100644 --- a/translation/dest/faq/os-SE.xml +++ b/translation/dest/faq/os-SE.xml @@ -3,27 +3,64 @@ АрФар Арæх Фарстатæ Lichess-æн йæ ном цæмæн у \"Lichess\"? - \"Lichess\" у \"live/light/libre\" æмæ \"chess\" — удæгас, рог æмæ сæрибар шахмæттæ. Дзурын хъæуы афтæ: %1$s. + \"Lichess\" у \"live/light/libre\" æмæ \"chess\"-ы комбинаци — удæгас, рог æмæ сæрибар шахмæттæ. Дзурын хъæуы афтæ: %1$s. личец - Байхъус специалист æй куыд дзуры. - Удæгас уый тыххæй, æмæ ды хъазыс æмæ кæсыс хъазтытæ реалон рæстæджы цыфæнды афоны; рог æмæ сæрибар уымæн, æмæ Lichess æргом æмæ быронæй сыгъдæг у, иннæ æргъыл сайтытæм нæ кæсгæйæ. - Уый дæр хауы Lichess-ы райдайæн кодмæ, %1$s-мæ, кæй нысан кæны li[chess in sca]la, уымæн æмæ Lichess у фыст %2$s-йæ, æнцонæмбарон программон æвзагæй. + Байхъус, специалист æй куыд дзуры. + Удæгас, уымæн æмæ дæ бон у хъазын æмæ кæсын хъæзтытæ реалон рæстæджы цыфæнды афоны; рог æмæ сæрибар, уымæн æмæ Lichess у æргом æмæ дзы нæй проприетарон быроны, иннæ коммерцион проектты хуызæн. + Уый дæр хауы Lichess-ы райдайæн кодмæ, %1$s-мæ, кæй нысан кæны li[chess in sca]la, уымæн æмæ Lichess сæйрагдæр фыст у %2$s-йæ, æнцонæмбарæн программон æвзагæй. Куыд баххуыс кæнон Lichess-æн? - Lichess цæры хъазджыты снывондытæй æмæ волонтёрты куыстæй. - Дæ бон у базонын %1$s, уый (æмæ %2$s дæр). Кæд дæ фæнды баххуысын Lichess-æн дæ рæстæгæй æмæ арæхстытæй, уæд дзы ис %3$s. + Lichess цæры хъазджыты нывондытæй æмæ волонтёрты куыстæй. + Дæ бон у базонын, %1$s (æмæ %2$s дæр). Кæд дæ фæнды баххуыс кæнын Lichess-æн дæ рæстæгæй æмæ арæхстытæй, уæд дзы ис %3$s. аххуысгæнæгæй куыд суын нæ хардзыты хыгъдлæвæрд æндæр амалтæ баххуыс кæнын - Ис дзы æндæр Lichess-вебсайтытæ? - О, Lichess ныфс радта æндæр æргом сайтытæм, кæй пайда кæнынц нæ %1$s, %2$s æмæ %3$s. + Ис дзы æндæр сайтытæ Lichess-æй бындурыл? + О, æндæр æргом сайтытæ Lichess-æй удцырын райстой æмæ пайда кæнынц нæ %1$s, %2$s æмæ %3$s. Рæстаг Хъазт - Сайæгой мæ кæд рамбулдзæн, уæд мын мæ рейтинг фæстæмæ ратдзыстут? - Хъазты арæзт - Нæмыг, блиц æмæ æндæр сахаты цæстдардтæ куыд рабæрæг конд ысты? + Сайæгой мæ кæд рамбылдта, уæд мын мæ рейтинг фæстæмæ ратдзыстут? + Сайæгойы кæд раргом кодтам, уæд, иу минутæйы фæстæ, йæ 40 ахæм рейтингимæ хъæзтытæ, фæстаг 3 боны кæм рамбылдта, уыдзысты равзард. Ды ацы хъæзтыты йæ ныхмæлæууæг кæд уыдтæ, æмæ дæхион рейтинг æууæнккаг кæд у, уæд дæ рейтинг дын фæстæмæ ратдзыстæм. Баххæст уыдзæн æрæнгонд дæ иуылстыр рейтингæй æмæ æнæраст хъæзтыты фæстæ рейтингы ивындзинæдтæй. + (Зæгъæм, дæ рейтинг ацы хъæзтыты фæстæ кæд тынг фæфылдæр, уæд, чи зоны, ницы дын баххæст кæндзыстæм, кæннод йæ хай æрмæст ратдзыстæм.) Цыфæндыйæ дæр дæ баххæст 150 гæбæлтæй нæ ахисдзæн. + Хъазджытæн, хъазтæй кæй рацæуынц æнæ хи дæттгæйæ, цы вæййы? + Дæ ныхмæлæууæг йæ хъазт кæд арæх фæуромы/фæуадзы, уæд йын хъæзтытæм бацæуæн рæстæгмæ æхгæд уыдзæн, æмæ йæ уæлдæй ничи фендзæн уый. Кæд уый фæстæ йæ митæ нæ бауромдзæн, уæд йын ногæй сыхгæндзыстæм хъæстытæм бацæуæн, фæлæ фылдæр æмгъуыдмæ. Æппын фæстаг, йæ аккаунт æххæстæй сыхгæндзыстæм. + Ис дзы мæ бон модераторæй суын? + Мах модераторы бынатмæ курдиаттæ нæ исæм. Кæд фендзыстæм, исчи, махмæ гæсгæ, уыдзæн хорз модератор, уæд нæхæдæг йемæ бастдзинад саразæм. + Ис дзы хицæндзинад фыстæгтæ фысгæйæ хъазтæй хуымæтæг хъазты \'хсæн? + Lichess-ы сæйраг хицæндзинад фыстæгтæ фысгæ хъазы уагæвæрдты у райдайæнты чиныг. Компьютеры программæты пайдакæнæн иваргонд у æмæ расайдзæн аккаунты æхгæнæн. ICCF-мæ нæкæсгæйæ, Lichess компьютеры программæты аххуыс нæ уадзы. + Хъазты æрфыст + Нæмыг, блиц æмæ æндæр хъазы хуызтæ сахаты цæстдардмæ гæсгæ куыд рабæрæг конд ысты? + Lichess сахаты цæстдард баст у æввахс хъазы дæргъцы = %1$s +Зæъæм, æввахс 5+3 хъазы дæргъц у 5 × 60 + 40 × 3 = 420 сикъунды. + (райдайæн рæстæг) + 40 × (уæлæмхасæн) < %1$s с = %2$s ≥ %1$s с = %2$s - Цавæр варианттæ мæ бон у хъазын Lichess-ы? + Цавæр шахмæтты хуызтæ мæ бон у хъазын Lichess-ы? Lichess-мæ ис классикон шахмæттæ æмæ %1$s. + 8 шахмæтты хуызты + «Рæстæмбис сантипешкæты сæфт» (ACPL) та ци у? + Сантипешкæ у барынады иуæг, кæй пайда у æвдисын уæлидзинад шахмæтты. Сантипешкæ у ¹/₁₀₀ пешкæйы аргъы. 100 сантипешкæты æмбæрц у 1 пешкæмæ. Ацы нысаниуæгтæ ницы давынц хъазты, фæлæ сты пайдаджын компьютерон шахмæтты æмæ позицийы аргъкæнынады компьютерон анализы. + +Фыццаг хаххы комьютеры цыд нысан кæны нулон сантипешкæты сафт, æмæ алы æндæр цыд фæкæны позицийы фæфыддæр, кæй бæрæг у сантипешкæты сафтæй. Цас фыддæр цыд у, уыйас фылдæр сафты нысаниуæг. + +Ацы нысаниуæг у хъазты бæрцы æвдисæн. Цас каддæр у сантипешкæты сæфт цыды рæстæмбисæй, уыйас хъаруджындæр у хъазт. + +Lichess компьютерон анализы пайда кæны Stockfish. + Æмбылд рæстæгæй, хæрæгсаст æмæ æрмæджы хъуаг + Фылдæр хатт, хъазæджы рæстæг куы фæуы, уæд йын фембылд банымайæм. Фæлæ хъазт хæрæгсастæй фæуы, ныхмæлæууæгмæ кæд нæй фадаты мат нывварын дарддæр хъазгæйæ (%1$s). + +Хатгай зын вæййы уынаффæ хæдархайгæ рахæссын (æнæбары цыдты сери, фидар). Æмткæй райсгæйæ мах райсæм фарс хъазæджы, кæмæн ма баззадис рæстæг. + +Хъуыдыйы дар, ныхмæлæууæгмæ кæд ис фигурæ кæнæ пешкæ, йæ бон кæмæн у къаролæн блокадæ сывæрын, уæд мат нывварын фадат ис иу бæхæй дæр, пылæй дæр. + FIDE-йы уагæвæрдтæ %s + Цæмæн пешкæйæн байст ис, цæвгæгонд быдырыл кæд нырид ахизыд, уæд? (ацæуæны байст) + Уый у цыд, зындгонд номæй «ацæуæны байст». Уацхъуыд уырыссаг Википедийы дæтты %1$s. + +Он описан в разделе 3.7 (d), %2$s: + +«Пешка, занимающая поле на той же горизонтали и на соседней вертикали с пешкой соперника, которая только что продвинулась на два поля в один ход от своей первоначальной позиции, может взять эту продвинутую пешку соперника, как если бы последний ее ход был только на одно поле. Это взятие возможно только ходом, следующим за этим продвижением, и называется взятием «на проходе».» + + Посмотрите %3$s и попрактикуйтесь. + хорз райхæлд + Æртывæр позицийы фæлхатын Ис дзы гæнæн мын раттын Lichess Мастеры (LM) хуындад? Нæй. Уый у каджын хуындад, æнæофициалон, æмæ æрмæст вæййы Lichess-ы. diff --git a/translation/dest/learn/be-BY.xml b/translation/dest/learn/be-BY.xml index b7a7719b13..7eb925d277 100644 --- a/translation/dest/learn/be-BY.xml +++ b/translation/dest/learn/be-BY.xml @@ -11,7 +11,7 @@ Ладдзя Рухаецца па простых лініях Ладдзя — гэта цяжкая фігура. Ці гатовы вы кіраваць ёй? - Пстрыкніце на ладдзю і давядзіце яе да зоркі! + Націсніце на ладдзю і давядзіце яе да зорачкі! Збярыце ўсе зоркі! Чым менш хадоў вы зробіце, тым больш балаў атрымаеце! @@ -35,7 +35,7 @@ І апошні! Вы навучыліся хадзіць каралём! Конь - Ён ходзіць літарай \"Г\" + Xодзіць літарай \"Г\" Асвоіць каня не так проста, бо ён — хітрая фігура. Коні маюць мудрагелісты спосаб пераскокваць! Коні могуць скакаць праз перашкоды! @@ -124,9 +124,9 @@ Абараніце свайго караля і выведзіце ў бой ладдзю! Перасуньце свайго караля на дзве клеткі да ладдзі каралеўскага фланга! Перасуньце свайго караля на дзве клеткі да ладдзі ферзевага фланга! - Конь у дарозе! Зрабіце ход канём, потым зрабіце ракіроўку. - Кароткая ракіроўка! Спачатку вам трэба развіць фігуры. - Доўгая ракіроўка! Спачатку вам трэба развіць фігуры. + Конь перашкаджае! Пахадзіце канём, потым зрабіце ракіроўку. + Кароткая ракіроўка! Спачатку вам трэба вывесці фігуры. + Доўгая ракіроўка! Спачатку вам трэба вывесці фігуры. Вы не можаце рабіць ракіроўку, калі кароль ці ладдзя, якія ўдзельнічаюць у ракіроўцы, рабілі ход. @@ -151,7 +151,7 @@ Гульня з\'яўляецца нічыйнай Калі гульцу не пастаўлены шах і няма магчымасці зрабіць ход, гэта пат. Гульня сканчаецца: без пераможцаў і пераможаных. Пат чорным: -- Чорныя не могуць схадзіць. +- Чорныя не могуць пахадзіць. - Кароль не пад шахам. Віншуем! Лепш атрымаць пат, чым мат! Прасунуты этап @@ -167,12 +167,11 @@ Атакуйце фігуры з больш высокай каштоўнасцю! Ферзь > Слон Вазьміце найбольш каштоўную фігуру! -Не разменьвайце больше каштоўную фігуру на менш каштоўную. +Не разменьвайце больш каштоўную фігуру на менш каштоўную. Вазьміце найбольш каштоўную фігуру! Пераканайцеся, што ход магчымы! - Атакуйце фігуры -з больш высокай каштоўнасцю! + Вазьміце найкаштоўнейшую фігуру! Віншуем! Вы ведаеце каштоўнасць фігур! Ферзь = 9 пешак Ладдзя= 5 пешак diff --git a/translation/dest/learn/fr-FR.xml b/translation/dest/learn/fr-FR.xml index 6ecc924c2a..044bf257b8 100644 --- a/translation/dest/learn/fr-FR.xml +++ b/translation/dest/learn/fr-FR.xml @@ -185,7 +185,7 @@ Fou = 3 Cavalier = 3 Pion = 1 Échec en deux coups - Donnez un échec en deux coups + Mettez échec en deux coups Trouvez la bonne combinaison en deux coups qui met le Roi de l\'adversaire en échec ! Menacez le Roi adverse en deux coups ! diff --git a/translation/dest/learn/or-IN.xml b/translation/dest/learn/or-IN.xml index 503721090f..7f3b6d6b5b 100644 --- a/translation/dest/learn/or-IN.xml +++ b/translation/dest/learn/or-IN.xml @@ -5,24 +5,25 @@ ରାଣୀ ରାଣୀ = ଡଙ୍ଗା + ହାତୀ ରଜା - ଘୋଡ଼ା + ଘୋଡ଼ା ସୈନିକ ବିକଶିତ - ଉଚ୍ଚ ଗତିଶୀଳତା ଥିବା ଗୋଟିଗୁଡ଼ିକର ଅଧିକ ମୂଲ୍ୟ ଅଛି! + ଉଚ୍ଚ ଗତିଶୀଳତା ଥିବା ଗୋଟିଗୁଡ଼ିକର ଅଧିକ ମୂଲ୍ୟ ଅଛି! ରାଣୀ = 9 ଡଙ୍ଗା = 5 ହାତୀ = 3 -ଘୋଡ଼ା = 3 +ଘୋଡ଼ା = 3 ସୈନିକ = 1 ରାଜା ଅମୂଲ୍ୟ ଅଟେ! ଏହାକୁ ହରାଇବାର ଅର୍ଥ ହେଉଛି ଖେଳ ହାରିବା। ଅଭିନନ୍ଦନ! ଆପଣ ମୋହରା ର ମୂଲ୍ୟ ଜାଣନ୍ତି! ରାଣୀ = 9 ଡଙ୍ଗା = 5 ହାତୀ = 3 -ଘୋଡ଼ା = 3 +ଘୋଡ଼ା = 3 ସୈନିକ = 1 ପ୍ରହେଳିକା ଆପଣଙ୍କ କୌଶଳ ଅଭ୍ୟାସ କରନ୍ତୁ + ଆପଣଙ୍କ ସ୍କୋର୍ ପ୍ରହେଳିକା ବିଫଳ ହେଲା! ପୁନଃଚେଷ୍ଟା କରନ୍ତୁ diff --git a/translation/dest/patron/be-BY.xml b/translation/dest/patron/be-BY.xml index 3119ff4d6c..dee03b4a47 100644 --- a/translation/dest/patron/be-BY.xml +++ b/translation/dest/patron/be-BY.xml @@ -58,7 +58,7 @@ Спонсар Lichess на працягу %s месяцаў Вы ахвяруеце lichess.org %s кожны месяц. - Дзякуй вілікі за дапамогу! Вы неверагодны! + Дзякуй вялікі за дапамогу! Вы неверагодны! Бягучы статус Наступны плацёж %2$s вы заплаціце %1$s. diff --git a/translation/dest/patron/de-CH.xml b/translation/dest/patron/de-CH.xml index 9925eb7045..19838f1ad1 100644 --- a/translation/dest/patron/de-CH.xml +++ b/translation/dest/patron/de-CH.xml @@ -1,7 +1,7 @@ Schpändä - Spende als %s + Spändä als %s Lichess Patroon Kostenloses Konto Wird Lichess Patroon diff --git a/translation/dest/patron/or-IN.xml b/translation/dest/patron/or-IN.xml index dcd51f23e9..aff9b6604c 100644 --- a/translation/dest/patron/or-IN.xml +++ b/translation/dest/patron/or-IN.xml @@ -1,7 +1,7 @@ ଦାନ କରନ୍ତୁ - %s ଭାବରେ ଦାନ କରନ୍ତୁ + %s ଭାବରେ ଦାନ କରନ୍ତୁ Lichess Patron ମାଗଣା ଖାତା ଏକ Lichess Patron ହୁଅନ୍ତୁ @@ -17,9 +17,9 @@ ମାସିକ ଅନ୍ୟ ଦୟାକରି %s ରେ ଏକ ରାଶି ପ୍ରବେଶ କରନ୍ତୁ - କ୍ରେଡ଼ିଟ୍ କାର୍ଡ଼ + କ୍ରେଡ଼ିଟ୍ କାର୍ଡ଼ PayPal - ଟଙ୍କା କୁଆଡ଼େ ଯାଏ? + ଟଙ୍କା କୁଆଡ଼େ ଯାଏ? ସର୍ବପ୍ରଥମେ, ଶକ୍ତିଶାଳୀ ସର୍ଭର। ତା\'ପରେ ଆମେ ଏକ ପୂର୍ଣ୍ଣକାଳୀନ ବିକାଶକାରୀ: %sଙ୍କୁ, Lichess ର ପ୍ରତିଷ୍ଠାତା, ଦେୟ ଦେଇଥାଉ। Lichess ସମର୍ଥନ ସହ ଯୋଗାଯୋଗ କରନ୍ତୁ @@ -35,7 +35,7 @@ ଆପଣଙ୍କ ସମର୍ଥନ ପ୍ରତ୍ୟାହାର କରନ୍ତୁ %1$s ବା %2$s Lichess ଆପଣଙ୍କ ପାଇଁ କେତେ ମୂଲ୍ୟବାନ ତାହା ସ୍ଥିର କରନ୍ତୁ: - ଆପଣଙ୍କ କ୍ରେଡ଼ିଟ୍ କାର୍ଡ଼ ପ୍ରତ୍ୟାହାର କରନ୍ତୁ ଏବଂ ଦେୟ ବନ୍ଦ କରନ୍ତୁ: + ଆପଣଙ୍କ କ୍ରେଡ଼ିଟ୍ କାର୍ଡ଼ ପ୍ରତ୍ୟାହାର କରନ୍ତୁ ଏବଂ ଦେୟ ବନ୍ଦ କରନ୍ତୁ: ଆଉ Lichess କୁ ସମର୍ଥନ କରନ୍ତି ନାହିଁ ତାରିଖ ରାଶି diff --git a/translation/dest/preferences/ar-SA.xml b/translation/dest/preferences/ar-SA.xml index 471302a527..3aed58d867 100644 --- a/translation/dest/preferences/ar-SA.xml +++ b/translation/dest/preferences/ar-SA.xml @@ -12,6 +12,7 @@ رمز قطعة الشطرنج حروف (K, Q, R, B, N) وضع التأمل + إظهار تقييمات اللاعب أظهر زر تعديل حجم الرقعة خلال الوضع المبدئي فقط (معصوب العينين (إخفاء القطع diff --git a/translation/dest/preferences/de-CH.xml b/translation/dest/preferences/de-CH.xml index effdce4b3c..fa46da7c00 100644 --- a/translation/dest/preferences/de-CH.xml +++ b/translation/dest/preferences/de-CH.xml @@ -12,6 +12,7 @@ Schachfiguräsymbool Buächschtabä (K, Q, R, B, N) Zän Modus + Wertigszahl vo Spieler ahzeigä Regler zum Ändärä vodä Brättgrössi aazeigä Nur idä Aafangsschtellig Blindschach (unsichtbari Figurä) diff --git a/translation/dest/preferences/id-ID.xml b/translation/dest/preferences/id-ID.xml index 1832005ca1..8707e938fc 100644 --- a/translation/dest/preferences/id-ID.xml +++ b/translation/dest/preferences/id-ID.xml @@ -12,6 +12,7 @@ Simbol bidak catur Huruf (K, Q, R, B, N) Moda Zen + Munculkan rating pemain Tampilkan pengubah ukuran papan catur Hanya di posisi awal Catur buta (buah catur disembunyikan) diff --git a/translation/dest/preferences/kk-KZ.xml b/translation/dest/preferences/kk-KZ.xml index bebcd229fa..85cf551514 100644 --- a/translation/dest/preferences/kk-KZ.xml +++ b/translation/dest/preferences/kk-KZ.xml @@ -11,7 +11,8 @@ Жүрісті жазу тәртібі Тас таңбасы Әріптер (K, Q, R, B, N) - Zen қалпы + Оқшау көрініс + Ойыншылардың рейтингін көрсету Тақтаны үлкейту тұтқасын көрсету Тек бастапқы күйде ғана Бүркемелі шахмат (көрінбейтін тастар) @@ -28,7 +29,7 @@ Екі әдіспен де Алдын-ала жүру (қарсыластың жүру кезегі кезінде жүру) Жүрісті қайтару (қарсыластың келісімі қажет) - Қалыпты ойындарда ғана + Жай ойындарда ғана Уәзірге автоматты түрде айналдыру Алдын-ала жүрісте Үш реттік қайталану болса, автоматты түрде тепе-теңдік жариялау diff --git a/translation/dest/preferences/ko-KR.xml b/translation/dest/preferences/ko-KR.xml index 11e92654ff..2b4e72fde5 100644 --- a/translation/dest/preferences/ko-KR.xml +++ b/translation/dest/preferences/ko-KR.xml @@ -12,6 +12,7 @@ 체스 말 기호 글자 (K, Q, R, B, N) 젠 모드 + 플레이어 레이팅 보기 보드 크기 재조정 핸들 보이기 초기 상태에서만 눈을 가리고 체스 (기물이 보이지 않음) diff --git a/translation/dest/preferences/vi-VN.xml b/translation/dest/preferences/vi-VN.xml index 0db6e5698d..ce506d36e8 100644 --- a/translation/dest/preferences/vi-VN.xml +++ b/translation/dest/preferences/vi-VN.xml @@ -12,6 +12,7 @@ Biểu tượng quân cờ Ký tự (K, Q, R, B, N) Chế độ Zen + Hiện xếp hạng của người chơi Hiện nút thay đổi kích cỡ Chỉ ở vị trí khởi tạo Cờ tưởng (quân cờ vô hình) diff --git a/translation/dest/puzzle/en-US.xml b/translation/dest/puzzle/en-US.xml index a0e353ca70..cf50c6b8e9 100644 --- a/translation/dest/puzzle/en-US.xml +++ b/translation/dest/puzzle/en-US.xml @@ -79,7 +79,7 @@ Play rapid and classical games to increase your chances of having a puzzle of yo %s to replay %s to replay - %s solved + %s solved Nothing to show, go play some puzzles first! Train these to optimize your progress! You perform the best in these themes diff --git a/translation/dest/puzzle/hi-IN.xml b/translation/dest/puzzle/hi-IN.xml index 882cad68ff..4fe2b415d1 100644 --- a/translation/dest/puzzle/hi-IN.xml +++ b/translation/dest/puzzle/hi-IN.xml @@ -13,6 +13,7 @@ विशेष चाल क्या आपको यह पहेली पसंद आई? अगले एक को लोड करने के लिए वोट दें! + आपकी पज़ल रेटिङ नही बदलेगी।पज़ल एक स्पर्धा नही है रेटिङ आपके अनुकुल पज़ल चुनने मे मदद करती है। सफेद रंग के लिए सबसे अच्छी चाल का पता लगाएं। काले रंग के लिए सबसे अच्छी चाल का पता लगाएं। वैयक्तिकृत पहेलियाँ प्राप्त करने के लिए: @@ -62,4 +63,7 @@ अपने कॉम्बो को संरक्षित करने के लिए इस कदम को छोड़ दें। सम्पूर्ण पारी मे केवल एक बार काम करता है । श्रंखला बनाए रखें । नई श्रंखला + मेरे खेलों से + %s के खेलो की पज़ल​| + पज़ल खोजे diff --git a/translation/dest/puzzle/ko-KR.xml b/translation/dest/puzzle/ko-KR.xml index 1f847f15e8..cc62a951d1 100644 --- a/translation/dest/puzzle/ko-KR.xml +++ b/translation/dest/puzzle/ko-KR.xml @@ -13,6 +13,7 @@ 특수 행마 이 퍼즐이 괜찮았나요? 다음 퍼즐을 위해 투표해주세요! + 당신의 퍼즐 레이팅은 바뀌지 않을 것입니다. 퍼즐은 경쟁이 아니라는 걸 기억하세요. 레이팅은 당신의 현재 스킬에 맞는 퍼즐을 선택하도록 돕습니다. 백의 최고의 수를 찾아보세요. 흑의 최고의 수를 찾아보세요. 개인화된 퍼즐을 위해선: @@ -73,7 +74,7 @@ %s 다시 풀기 - %s 해결함 + %s 해결함 표시할 것이 없습니다. 먼저 퍼즐을 플레이하세요! 실력을 높이기 위해 훈련하세요! 이 테마에서 최고의 성적을 냈습니다 diff --git a/translation/dest/puzzleTheme/fa-IR.xml b/translation/dest/puzzleTheme/fa-IR.xml index 5c861b6793..df97d18378 100644 --- a/translation/dest/puzzleTheme/fa-IR.xml +++ b/translation/dest/puzzleTheme/fa-IR.xml @@ -1,17 +1,28 @@ پیاده پیش رفته + یکی از پیاده های شما در زمین حریف پیشروی کرده است. شاید خطر ارتقا پیاده وجود داشته باشد. برتری مات آناستازیا + یک اسب و یک رخ به همدیگر کمک میکنند تا شاه حریف را بین گوشه های زمین و یک مهره از حریف زندانی کنند. مات عربی یک اسب و یک رخ برای به دام انداختن شاه حریف در گوشه صفحه همکاری می کنند. حمله به خانه f2 یا f7 + حمله ای که در آن روی پیاده های f2 و f7 تمرکز می شود، مانند دفاع دو اسب. جلب کردن مات عرض آخر به دام انداختن شاه حریف در عرض اولیه خود زمانی که با مهره های خودی به دام افتاده است. آخر بازی فیل + یک آخر بازی تنها با فیل ها و پیاده ها. + مات Boden + دو فیل به حالت ضربدری که به گوشه زمین حمله می کنند یک شاه که راهش با مهره های خودش سد شده را مات می کنند. قلعه رفتن شاه خود را ایمن کنید و رخ خود را برای حمله مستقر کنید. + مهره دفاع کننده را بگیرید + گرفتن یک مهره که برای دفاع از یک مهره دیگر حیاتی است، اجازه می دهد مهره ای که اکنون بدون دفاع است در حرکت بعدی گرفته شود. + مات با دو فیل + دو فیل که همزمان به گوشه های مجاور حمله می کنند، شاهی که راهش با مهره های خودش سد شده را مات می کنند. + وزیری که شاه مجاور خودش را که تنها دو خانه ای که برای فرارش باقی مانده توسط مهره های خودش سد شده، مات می کند. برابری حمله به جناه شاه حمله به شاه حریف زمانی که در جناه شاه قلعه رفته است. @@ -19,6 +30,7 @@ حرکت تدافعی منحرف کردن حمله برخاستی + حرکت دادن یک مهره (مانند اسب)، که قبلا جلوی حمله مهره خودمان به یکی از مهره های دور حریف (مانند رخ)، از جلوی راه مهره حمله کننده. کیش دوگانه کیش دادن به حریف با دو مهره به صورت هم زمان در نتیجهء یک حمله برخاستی که در آن هم مهره برخاست کننده و هم مهره پشت سر آن به شاه حریف حمله می کنند. آخر بازی @@ -28,9 +40,19 @@ چنگال حرکتی که در آن مهره ای که حرکت می کند دو مهره حریف را به صورت همزمان مورد حمله قرار می دهد. مهره بی دفاع + تاکتیکی که در آن مهره های حریف برای گرفتن، بدون دفاع یا با دفاع ناکافی است. + مات Hook + مات با یک رخ، اسب و یک پیاده در برابر یک پیاده حریف برای محدود کردن راه های فرار شاه دشمن. + چنگال + حرکت دادن یک مهره و حمله کردن به دو مهره هم زمان که بتوان یکی از مهره ها را گرفت، مانند حمله یک اسب به به دو تا رخ بصورت هم زمان. + آخر بازی اسب + یک آخر بازی تنها با اسب ها و پیاده ها. معمای طولانی + سه حرکت برای پیروزی. بازی‌های اساتید + پازل های برگرفته شده از بازی هایی که توسط بازی کنان دارای عنوان انجام شده است. بازیهای استاد در مقابل استاد + پازل های برگرفته شده از بازی های بین دو بازی کن دارای عنوان. کیش و مات بازی را با سبک خاصی ببرید. مات در یک حرکت diff --git a/translation/dest/puzzleTheme/kk-KZ.xml b/translation/dest/puzzleTheme/kk-KZ.xml index f0e9c0ec01..b3bcb52bb4 100644 --- a/translation/dest/puzzleTheme/kk-KZ.xml +++ b/translation/dest/puzzleTheme/kk-KZ.xml @@ -2,7 +2,7 @@ Әккі сарбаз Айналуға ұмтылған не айналу қаупін төндіретін сарбаз – тактиканың өзегі. - Артықшылық + Басымдылық Шешуші артықшылық беретін сәтті жіберіп алмаңыз. (200-600 сп) Анастасия маты Ат пен тура не уәзірдің бірлесіп, қарсылас патшасын тақта шеті мен өз тасының арасында ұстап алуы. diff --git a/translation/dest/settings/hi-IN.xml b/translation/dest/settings/hi-IN.xml index e007e57345..5ca28787e5 100644 --- a/translation/dest/settings/hi-IN.xml +++ b/translation/dest/settings/hi-IN.xml @@ -3,7 +3,7 @@ व्यवस्था (सेटिंग्स) खातें को बंद करें समापन निश्चित है। वहां से कोई वापसी नहीं है। क्या आपने सुनिश्चित कर लिया है? - आपको एक ही नाम से एक नया खाता खोलने की अनुमति नहीं दी जाएगी, भले ही मामला अलग हो। + आपको एक ही नाम से एक नया खाता खोलने की अनुमति नहीं दी जाएगी, भले ही अक्षर बड़े-छोटे हो। मैंने अपना मन बदल लिया, मेरे खाता को बंद न करें क्या आप वाकई अपना खाता बंद करना चाहते हैं? अपना खाता बंद करना एक स्थायी निर्णय है। आप कभी भी लॉग इन नहीं कर पाएंगे। यह ख़ाता बंद है| diff --git a/translation/dest/site/af-ZA.xml b/translation/dest/site/af-ZA.xml index 2543ffb7eb..e5acce2d21 100644 --- a/translation/dest/site/af-ZA.xml +++ b/translation/dest/site/af-ZA.xml @@ -48,13 +48,15 @@ Wit het die spel verlaat Swart het die spel verlaat WitHetniebeweegnie + Swart het nie geskuif nie Versoek \'n rekenaar analise Rekenaar analise Rekenaar analise beskikbaar + Rekenaaranalise gedeaktiveer Ontledingsbord Diepte %s Die gebruik van bediener analise - Enjin is besig om the laai ... + Enjin laai tans ... Wolkanalise Gaan dieper Toon bedreiging @@ -68,7 +70,7 @@ Variant verloor Variant wen Onvoldoende materiaal - Pion skuif + Pionskuif Buiting Maak toe Besig om te wen @@ -85,12 +87,17 @@ Mat in %s half-skuif Mat in %s half-skuiwe + DTZ50\" met afronding, gebaseer op die hoeveelheid half-skuiwe tot die volgende vat of pionskuif Geen spel gevind Voeg dalk meer wedstryde van die voorkeure kieslys by? Opening ontdekker + Opening/eindspel ontdekker %s opening ontdekker Oorwinning gekelder deur 50-skuifreël Verloor voorkom deur 50-skuifreël + Wen of 50 skuiwe deur vorige fout + Verloor of 50 skuiwe deur vorige fout + Wen/verloor slegs gewaarborg as aanbevole tafelbasis lyn gevolg is sedert die laaste vat of pion skuif, weens moontlike afronding van DTZ waardes in Syzygy tafelbasisse. Gereed! Voer PGN in Skrap @@ -110,6 +117,18 @@ Flater Fout Onakkuraatheid + + %s flater + %s flaters + + + %s fout + %s foute + + + %s onakuraatheid + %s onakuraathede + Skuif tye Keer bord om Drie-malige herhaling @@ -120,6 +139,8 @@ %s speler %s spelers + Gelykop deur wedersydse ooreenkoms + Vyftig skuiwe sonder vordering Lopende spelle Sien al %s spelle @@ -142,7 +163,7 @@ Spelers Vriende Gesprekke - Vandag + Vandag Gister Minute per kant Variant @@ -273,7 +294,7 @@ Boekmerk die spel Toernooi Toernooie - Toernooi punte + Toernooipunte %s toernooipunt %s toernooipunte @@ -330,7 +351,7 @@ Plak \'n wedstryd PGN om dit deursoekbaar te herspeel, rekenaar analise, kletskamer en deelbare URL te kry. - %s het spel ingevoer + %s ingevoerde spel %s het spelle ingetrek Hierdie is \'n skaak CAPTCHA. @@ -349,6 +370,8 @@ rekenaar analise, kletskamer en deelbare URL te kry. Volg Besig om te volg Ontvolg + Volg %s + Ontvolg %s Blokeer Geblok Ontblok @@ -369,7 +392,7 @@ rekenaar analise, kletskamer en deelbare URL te kry. Lys Grafiek - Minder as %s minute + Minder as %s minuut Minder as %s minute Vereiste. @@ -411,9 +434,11 @@ rekenaar analise, kletskamer en deelbare URL te kry. Bord redigeerder Pak die bord Gewilde openinge - Begin posisie + Eindspel posisies + Skaak960 begin posisie: %s + Begin posisie Maak bord skoon - Laai posisie + Laai posisie Privaat Raporteer %s aan moderators Profiel voltooiing: %s @@ -425,13 +450,14 @@ rekenaar analise, kletskamer en deelbare URL te kry. Noem naam Van Biografie + Land of vlag Dankie! Sosiale media skakels Binnelyn notasie Voorigekeer op Lichess TV Aanlyn spelers Aktiewe spelers - Pasop, dié spel is gegradeer, maar sonder \'n klok! + Pasop, dié spel is gegradeer, maar sonder \'n klok! Sukses %s spel aan die gang @@ -443,6 +469,8 @@ rekenaar analise, kletskamer en deelbare URL te kry. Toernooi wenners Naam Beskrywing + Private beskrywing + Teks wat slegs spanlede sal sien. Indien gestel vervang dit die publieke beskrywing vir spanlede. Nee Ja Help: @@ -463,6 +491,7 @@ rekenaar analise, kletskamer en deelbare URL te kry. Kul Beledig Boelie + Gradering manipulasie Ander Plak skakel na die spel(le) en verduidelik wat skort met die lid se gedrag. Moenie net sê hulle kroek nie, maar verduidelik hoe daardie gevolgtrekking bereik is. Jou verslag sal vinniger geantwoord word as dit in Engels geskryf is. Verskaf asseblief ten minste een skakel na \'n spel waar hulle gekroek het. @@ -497,7 +526,7 @@ rekenaar analise, kletskamer en deelbare URL te kry. Op stadig spelle Altyd Nooit - %1$s kompeteer in %2$s + %1$s kompeteer in %2$s Oorwinning Nederlaag %1$s vs %2$s in %3$s @@ -528,7 +557,16 @@ rekenaar analise, kletskamer en deelbare URL te kry. Gereedskap Inkrement Dié veld word vereis + Hierdie epos adres is ongeldig + Hierdie e-pos adres is nie aanvaarbaar nie. Maak seker dis reg en probeer weer. + E-pos adres is ongeldig of reeds geneem + Hierdie is reeds jou e-pos adres + Moet ten minste %s karakters lank wees + Moet op die meeste %s karakters lank wees + Moet minstens %s wees + Moet meestens %s wees Indien gradering ± %s is + Slegs bestaande gesprekke Slegs vriende Kieslys Kastelering @@ -615,6 +653,7 @@ rekenaar analise, kletskamer en deelbare URL te kry. Jy kan ook blaai oor die bord om te beweeg in die spel. Druk shift+klik of regs-kliek om sirkels en pyle te trek op die bord. Laat ander spelers boodskappe aan jou stuur + Laat weet wanneer in die forum genoem word Deel jou data oor Skaak Insigte Met niemand Met vriende @@ -625,6 +664,7 @@ rekenaar analise, kletskamer en deelbare URL te kry. Skakel kindermodus aan Skakel kindermodus aan Sekuriteit + Sessies alle sessies herroep Speel skaak oral So gratis soos Lichess @@ -772,4 +812,57 @@ rekenaar analise, kletskamer en deelbare URL te kry. U kan nog nie in die forums plaas nie. Speel \'n paar speletjies! Skryf in Beëindig inskrywing + het jou genoem in \"%1$s\". + %1$s het jou genoem in \"%2$s\". + het jou genooi na \"%1$s\". + %1$s het jou genooi na \"%2$s\". + Jy is nou deel van die span. + Jy het by \"%1$s\" aangesluit. + Iemand wat jy aangemeld het, is verban + Baie geluk, jy het gewen! + Spel teen %1$s + %1$s teen %2$s + Iemand het jou afrigtersprofiel resenseer. + Nuwe hangende resensie + Jy het teen iemand verloor wat nie die Lichess bepalings nagekom het nie + Terugbetaling:%1$s %2$s graderingspunte. + Tyd is byna op! + [Klik om e-posadres te beskou] + Laai af + Welkom! + Lichess is \'n liefdadigheidsorganisasie en heeltemal gratis/libre oopbron sagteware. +Alle bestuurskostes, ontwikkeling en inhoud word heeltemal gefinansier deur lede bydraes. + Afrigter bestuurder + Uitsender bestuurder + Kanseleer die toernooi + Toernooi beskrywing + Enige iets spesiaal wat jy aan die deelnemers wil vertel? Probeer dit kort hou. \"Markdown\" skakels is beskikbaar: [name](https://url) + Spelle is gegradeer +en beïnvloed speler graderings + Slegs lede van span + Geen beperking + Minimum gegradeerde spelle + Minimum gradering + Maksimum weeklikse gradering + Slegs getitelde spelers + Benodig \'n amptelike titel om by die toernooi aan te sluit + Plak \'n geldige FEN om elke spel van \'n sekere posisie te begin. +Dit werk slegs met standaard spelle, nie met variante nie. +Jy kan die %s gebruik om \'n FEN posisie te genereer, en dit dan hier plak. +Laat dit leeg om elke spel vanaf die normale posisie te begin. + Kanseleer die simul + Gasheer kleur vir elke spel + Verwagte begin tyd + Vertoon op %s + Wys jou simul aan almal op %s. Deaktiveer vir private simuls. + Simul beskrywing + Enige iets wat jy vir die deelnemers wil vertel? + %s is beskikbaar vir meer ingewikkelde sintaks. + Plak \'n spel URL of a studie hoofstuk URL om dit in te bed. + In you eie plaaslike tydsone + Toernooi klets + Geen klets + Slegs spanleiers + Slegs spanlede + Speel die beste rekenaarskuif diff --git a/translation/dest/site/be-BY.xml b/translation/dest/site/be-BY.xml index 6d35a47d61..1c42e6686f 100644 --- a/translation/dest/site/be-BY.xml +++ b/translation/dest/site/be-BY.xml @@ -551,7 +551,7 @@ Махінацыі з рэйтынгам Іншае Пакіньце ніжэй спасылку на гульню (ці гульні) і патлумачце, што вас непакоіць у паводзінах гэтага карыстальніка. Не пішыце нешта кшталту «ён чмут!» – патлумачце, як вы прыйшлі да гэтага выніку. Мы хутчэй разбярэмся ў сітуацыі, калі вы напішаце нам па-англійску. - Калі ласка, дадайце спасылку сама менш на адну гульню, дзе, на вашу думку, былі парушаны правілы. + Калі ласка, дадайце спасылку хаця б на адну гульню, дзе былі парушаны правілы. аўтар: %s Тэма закрытая Блог @@ -757,7 +757,7 @@ Хуткі старт Лобі Ананімны гулец - Ваш вынік: %s + Ваш лік: %s Мова Фон Светлы @@ -856,8 +856,8 @@ Мы просім прабачэння за часовыя нязручнасці, і жадаем вам вельмі добрых гульняў на lichess.org. Дзякуй за ўвагу! - Рахунак за ўсё время - Рахунак у бягучым матчы + Лік за ўвесь час + Лік бягучага матчу Я згаджаюся, што я ніколі не скарыстаюся старонняй дапамогай у сваіх гульнях (ад шахматных праграм, кніг, баз даных або іншых гульцоў). Я згаджаюся, што я заўсёды адносіцца з павагай да другіх гульцоў. Я згаджаюся, што я не буду ствараць некалькі ўліковых запісаў. diff --git a/translation/dest/site/bn-BD.xml b/translation/dest/site/bn-BD.xml index 624697ab0d..13654c45e2 100644 --- a/translation/dest/site/bn-BD.xml +++ b/translation/dest/site/bn-BD.xml @@ -47,9 +47,12 @@ কালো হার মেনে নিলেন সাদা খেলাটি ছেড়ে চলে গেছেন কালো খেলাটি ছেড়ে চলে গেছেন + সাদা চাল দিল না + কালো চাল দিল না কম্পিউটার বিশ্লেষণের জন্য অবেদন করুন কম্পিউটারের বিশ্লেষণ কম্পিউটার বিশ্লেষণ পাওয়া যায় + কম্পিউটার বিশ্লেষণ নিষ্ক্রিয় করা আছে বিশ্লেষণ বোর্ড গভীরতা %s সার্ভারের বিশ্লেষণ ব্যবহার করা হচ্ছে @@ -153,7 +156,7 @@ খেলোয়াড় বন্ধুরা বার্তাগুলি - আজ + আজ গতকাল প্রতিটি পাশের জন্য বরাদ্দ মিনিট বিকল্প @@ -360,6 +363,8 @@ অনুসরণ অনুসরণ করছেন আর অনুসরণ নয় + %sকে অননুসরণ করুন + %sকে অননুসরণ করুন বাধা দিন বাধাগ্রস্ত বাধা উঠিয়ে নিন @@ -422,9 +427,9 @@ বোর্ড সম্পাদক বোর্ড সেট করুন জনপ্রিয় চমক - শুরুর অবস্থান + শুরুর অবস্থান বোর্ড খালি করুন - অবস্থান পুনঃস্থাপন + অবস্থান পুনঃস্থাপন ব্যক্তিগত মডারেটরদের কাছে %s এর নামে রিপোর্ট করুন প্রোফাইল সম্পন্ন: %s @@ -443,7 +448,7 @@ এর আগে Lichess TV- তে যা হয়েছে যে সমস্ত খেলোয়ার অনলাইন আছে সক্রিয় খেলোয়াড়গণ - সাবধান, এই খেলাটি মূল্যায়িত তবে কোনো সময়সীমা নেই! + সাবধান, এই খেলাটি মূল্যায়িত তবে কোনো সময়সীমা নেই! সফল্য হয়েছে %s খেলা চলছে @@ -512,7 +517,7 @@ খেলায় ধীর গতি সব সময় কখনই না - %1$s করা আছে মধ্যে %2$s + %1$s করা আছে মধ্যে %2$s বিজয় হার %1$s বানাম %2$s মধ্যে %3$s @@ -543,6 +548,8 @@ সরঞ্জামসমূহ মুনাফা এই ঘরটি পূরন করা আবশ্যক + ইমেইল ঠিকানাটি বৈধ নয় + ইমেইল ঠিকানাটি গ্রহণযোগ্য নয়। দয়া করে পুনরায় চেক করুন, অত‍ঃপর চেষ্টা করুন। যদি অনুপাত হয় ± %s বিদ্যমান কথোপকথন শুধু বন্ধুরা @@ -790,7 +797,7 @@ আপনি এখন ফোরামে পোষ্ট করতে পারবেন না. আগে কিছু গেম খেলুন! সাবস্ক্রাইব আনসাবস্ক্রাইব - \"%1$s\" এ আপনাকে উল্লেখ করেছেন। + \"%1$s\" এ আপনাকে উল্লেখ করেছেন। %1$s , \"%2$s\" এ আপনার নাম উল্লেখ করেছেন। \"%1$s\" এ আপনাকে আমন্ত্রণ জানিয়েছেন। %1$s, আপনাকে \"%2$s\" এ আমন্ত্রণ জানিয়েছেন। diff --git a/translation/dest/site/br-FR.xml b/translation/dest/site/br-FR.xml index 862737901b..9968e1cad7 100644 --- a/translation/dest/site/br-FR.xml +++ b/translation/dest/site/br-FR.xml @@ -159,7 +159,7 @@ C\'hoarierien Mignoned Kaozeadennoù - Hiziv + Hiziv Dec\'h Munutennoù evit pep c\'hoarier Doare @@ -494,9 +494,9 @@ Kargit ul lec\'hiadur Digoradurioù brudet Plegennoù an dibenn-partienn - Plegenn orin + Plegenn orin Tennañ pep tra - Kargañ ul lec\'hiadur + Kargañ ul lec\'hiadur Prevez Diskleriañ %s d\'an habaskaerien Feur leuniañ ar profil: %s @@ -514,7 +514,7 @@ N\'eus ket pell zo war Lichess TV C\'hoarierien enlinenn C\'hoarierien oberiat - Diwallit, renket eo ar c\'hrogad met n\'eus horolaj ebet! + Diwallit, renket eo ar c\'hrogad met n\'eus horolaj ebet! Deuet oc\'h a-benn %s c\'hrogad o ren bremañ @@ -582,7 +582,7 @@ E-pad ar c\'hrogadoù difonn Bepred Morse - %1$s a gemer perzh e-barzh %2$s + %1$s a gemer perzh e-barzh %2$s Trec\'h Lamm %1$s a-enep %2$s e %3$s diff --git a/translation/dest/site/ckb-IR.xml b/translation/dest/site/ckb-IR.xml index 81efc5518a..fbaceff27b 100644 --- a/translation/dest/site/ckb-IR.xml +++ b/translation/dest/site/ckb-IR.xml @@ -7,6 +7,7 @@ چاوەڕوانی لایەینی بەرامبەر چاوەڕوانی ڕازیبوونی لایەنی بەرامبەر سەرەی تۆیە + %1$s ئاست %2$s پلە هێز چەسپاندنی چاتەکە @@ -23,6 +24,7 @@ یاری بە پارچە سپیەکان دەکەیت یاری بە پارچە ڕەشەکان دەکەیت سەرەی تۆیە! + دەرکەوت کە فێڵ کراوە پاشا لە ناوەڕاست سێ چێککردن (هێرش کردنە سەر پاشای بەرامبەر) کۆتایی هات @@ -41,20 +43,28 @@ رەش خۆی بەدەستەوەدا سپی وازیهێنا لە یاریکردن ڕەش وازیهێنا لە یاریکردن + سپی جووڵەی نەکرد + ڕەش جووڵەی نەکرد داواکردنی شیکاری کۆمپیوتەری شیکاری کۆمپیوتەری شیکاری کۆمپیوتەری بەردەستە + شیکاری کۆمپیوتەری ناچاڵاک کرا شیکاری تەختەی شەترەنج قوڵایی%s بەکارھێنانی شیکاری ڕاژەکاری بزوێنەرەکە لە باری هەڵگرتندایه... + شیکاری ئۆنڵاین زیاتر پیشاندانی مەترسی له شوێنی گەڕانی ناوخۆیی + هەڵسەنگاندنی خۆجێیی بگۆڕە + برەوپێدانی جۆراوجۆر کردن بە رستەی سەرەکی سڕینەوە + هێزی جۆراوجۆر جوڵان دۆرانی ناچاری + براوەی هەمەجۆری یەکسانبوون بەهۆی نەمانی جوڵە بۆ کش جوڵەی سەرباز گرتن @@ -68,16 +78,56 @@ پۆلێنکردنی تێکرا: %s دوایین یاریەکانت بەرزترین یاریەکان + دوو ملیۆن یاری سەر بۆرد لە لایەن %1$s+ FIDE-یاریزانانی هەڵسەنگنراو، کە خەڵکی %2$s بۆ %3$s + DTZ50\'\' لەگەڵ نزیکخستنەوەیان، بە پێی ژمارەی نیوەی جووڵەکان تا زاڵبوونێکی دیکە یان جوڵەی سەرباز هیچ یارییەک نەدۆزرایەوە ئایا دەتەویت جۆری تر دیاری بکەیت? + دۆزەرەوەی کردنەوەکان + دۆزەرەوەی کردنەوەکان\کۆتاییەکانی یاری + %s دۆزەرەوەی کردنەوەکان + براوە دیاری ناکرێت بە یاسای-50 جووڵە + دۆڕاو دیاری ناکرێت بە یاسای-50 جووڵە + براوە یان 50 جووڵە بەهۆی هەڵەی پێشوو + دۆڕاو یان 50 جووڵە بەهۆی هەڵەی پێشوو + براوە\دۆڕاو کاتێک دەستەبەر دەکرێت کە ئەوەی لە لایەن خشتەی سەر تەختەوە پێشنیار دەکرێت جێبەجێبکرێت، ئەوەش لە دواین زاڵبوون یان جوڵەی سەرباز دەێت، لەبەر ئەگەری نزیکخستنەوەی نرخی DTZ لە خشتەی سەر تەختای Syzygy. ئامادەیە! سکرین شوت PGN سڕینەوە دڵنیای کە دەتەوێت بیسڕیەوە? سەیرکردنەوەی یاریەکە وەک خۆی + بەهۆی CPL + خوێندنی کراوە + چالاککراوە + باشترین جووڵە بە تیر دیار بکە + پێوانەی هەڵسەنگاندن + زیادکردنی هێڵەکان + CPUs + Memory + شیکاریکردنی بێ کۆتا + لابردنی دورترین سنوور، بەمەش کۆمپیوتەرەکەت سەلامەتتر دەبێت + هەڵەی گەورە + هەڵە + ناتەواو + + %s هەڵەی گەورە + %s هەڵە گەورەکان + + + %s هەڵە + %s هەڵەکان + + + %s نا تەواو + %s ناتەواوەکان + + ژمارەی جوڵەکان + هەڵگێڕانەوەی تەختە + سێبارە کردنەوە + داوای یەکسانی بکە پێشکەشکردنی یەکسانبوون یەکسان + بە رەزاماندی هەردولا یەکسان بوون دەرچوون چوونە ناوە diff --git a/translation/dest/site/de-CH.xml b/translation/dest/site/de-CH.xml index d9a84d03f6..d0a5f6ac7b 100644 --- a/translation/dest/site/de-CH.xml +++ b/translation/dest/site/de-CH.xml @@ -87,7 +87,7 @@ Matt i %s Halbzug Matt i %s Halbzüg - DTZ50\'\' mit Rundung, basierend auf der Anzahl der Halbzüge bis zum nächsten Schlag- oder Bauernzug + DTZ50\'\' mit Runde, basierend uf dä Ahzahl Halbzüüg bis zum nächstä Schlag- oder Puurezug Käs Schpiil gfundä Villicht söted meh Schpiil vom Iischteligs Menu iibezogä werdä? Eröffnigsdatäbank @@ -95,9 +95,9 @@ %s Eröffnigsbuäch Sieg dur d 50-Züg-Reglä verhinderet Verluscht dur d 50-Züg-Reglä verhinderät - Sieg oder 50 Züge durch vorherigen Fehler + Siig odär 50 Züüg dur en vorherigä Fählär Verlust odär 50 Züüg dur en vorherigä Fählär - Gewinn/Verlust ist nur garantiert, falls die empfohlene Datenbank-Zugfolge seit dem letzten Schlag- oder Bauernzug befolgt wurde, da möglicherweise DTZ-Werte in den Syzygy-Datenbanken gerundet sind. + Gwünn/Verlust isch nur garantiert, falls diä empfohlänä Datebank-Zugfolge sit äm letztä Schlag- oder Puurezug befolgt worde sind, da möglicherwiis DTZ-Wärt ih de Syzygy-Datebank grundet sind. Färtig! PGN importierä Löschä @@ -163,7 +163,7 @@ Schpiller/-innä Fründä Konvärsazion - Hüt + Hüt Geschter Minutä pro Schpiller/-in Variantä @@ -435,9 +435,9 @@ Beliäbti Eröffnigä Ändschpiilpositionä Fischer Random Aafangsschtellig: %s - Aafangsposition + Aafangsposition Brätt löschä - Schtellig ladä + Schtellig ladä Privaat Mäld %s dä Moderatorä Profil-Vollschtändigkeit: %s @@ -456,7 +456,7 @@ Zletscht uf Lichess TV Schpiller/-innä online Aktivi Schpiller/-innä - Obacht, das Schpiil isch gwärtät, hät aber keis Ziitlimit! + Obacht, das Schpiil isch gwärtät, hät aber keis Ziitlimit! Jawoll %s laufändi Partii @@ -525,7 +525,7 @@ Bi langsamä Partiiä Schtändig Niä - %1$s nimmt bi %2$s teil + %1$s nimmt bi %2$s teil Siig Niderlag %1$s gäg %2$s in %3$s @@ -556,14 +556,14 @@ Wärchzüüg Inkremänt Das Fäld isch Pflicht - Diese E-Mail-Adresse ist ungültig - Diese E-Mail-Adresse ist nicht akzeptabel. Bitte überprüfe sie und versuche es erneut. - E-Mail-Adresse ungültig oder bereits vergeben - Dies ist bereits deine E-Mail Adresse - Mindestlänge ist %s - Maximallänge ist %s - Muss mindestens %s sein - Darf höchstens %s sein + Diä E-Mail-Adrässä isch ungültig + Diä E-Mail-Adrässe wird nöd akzeptiärt. Bitte überprüäf si und probiär dänn nomol. + Diä E-Mail-Adrässe isch invalid oder scho vergäh + Das isch scho dini E-Mail-Adrässä + Mues mindestens %s Zeichä lang sii + Mues höchstens %s Zeichä lang sii + Mues mindestens %s sii + Mues höchstens %s sii Nur, wänn d\'Wärtigszahl ± %s isch Nur bestehändi Konversazionä Nur Fründä @@ -812,7 +812,7 @@ Du chasch nonig idä Forä schriibä. Schpill zersch äs paar Partiiä! Abonniärä Deabonniärä - hät dich in \"%1$s\" ärwähnt. + hät dich in \"%1$s\" ärwähnt. %1$s hät dich in \"%2$s\" ärwähnt. hät dich zu \"%1$s\" iigladä. %1$s hät dich zu \"%2$s\" iigladä. diff --git a/translation/dest/site/de-DE.xml b/translation/dest/site/de-DE.xml index b107ff47d2..1b9dd78921 100644 --- a/translation/dest/site/de-DE.xml +++ b/translation/dest/site/de-DE.xml @@ -59,7 +59,7 @@ Engine wird geladen ... Cloud-Analyse Tiefe erhöhen - Bedrohungen zeigen + Drohungen anzeigen im lokalen Browser Lokale Auswertung an-/ausschalten Variante aufwerten @@ -650,7 +650,7 @@ Importiertes Spiel herunterladen Matchverlauf Du kannst auch auf dem Brett scrollen, um die Partie durchzugehen. - Fahre über Computervarianten, um eine Vorschau auf sie zu erhalten. + Bewege den Mauszeiger über Computervarianten, um eine Vorschau zu erhalten. Drücke Shift+Mausklick oder Rechtsklick, um Kreise und Pfeile auf dem Brett zu zeichnen. Erlaube anderen Spielern dich zu kontaktieren Benachrichtigungen erhalten, wenn du im Forum erwähnt wirst @@ -819,7 +819,7 @@ Du bist nun Teammitglied. Du bist \"%1$s\" beigetreten. Jemand, den du gemeldet hast, wurde gebannt - Herzlichen Glückwunsch, du hast gewonnen! + Glückwunsch, du hast gewonnen! Partie gegen %1$s %1$s gegen %2$s Jemand hat dein Trainerprofil bewertet. diff --git a/translation/dest/site/en-US.xml b/translation/dest/site/en-US.xml index 4881239dfc..08b4390a76 100644 --- a/translation/dest/site/en-US.xml +++ b/translation/dest/site/en-US.xml @@ -163,7 +163,7 @@ Players Friends Conversations - Today + Today Yesterday Minutes per side Variant @@ -436,9 +436,9 @@ computer analysis, game chat and shareable URL. Popular openings Endgame positions Chess960 start position: %s - Starting position + Starting position Clear board - Load position + Load position Private Report %s to moderators Profile completion: %s @@ -457,7 +457,7 @@ computer analysis, game chat and shareable URL. Previously on Lichess TV Online players Active players - Beware, the game is rated but has no clock! + Beware, the game is rated but has no clock! Success %s game in play @@ -526,7 +526,7 @@ computer analysis, game chat and shareable URL. On slow games Always Never - %1$s competes in %2$s + %1$s competes in %2$s Victory Defeat %1$s vs %2$s in %3$s @@ -561,10 +561,10 @@ computer analysis, game chat and shareable URL. This email address is not acceptable. Please double-check it, and try again. Email address invalid or already taken This is already your email address - Must be at least %s characters long + Must be at least %s characters long Must be at most %s characters long Must be greater or equal to %s - Must be at most %s + Must be at most %s If rating is ± %s Only existing conversations Only friends @@ -813,7 +813,7 @@ computer analysis, game chat and shareable URL. You can\'t post in the forums yet. Play some games! Subscribe Unsubscribe - mentioned you in \"%1$s\". + mentioned you in \"%1$s\". %1$s mentioned you in \"%2$s\". invited you to \"%1$s\". %1$s invited you to \"%2$s\". diff --git a/translation/dest/site/fi-FI.xml b/translation/dest/site/fi-FI.xml index aeab410e62..b27448cb0d 100644 --- a/translation/dest/site/fi-FI.xml +++ b/translation/dest/site/fi-FI.xml @@ -794,8 +794,8 @@ Klassinen Järjettömän nopeat pelit: alle 30 sekuntia Hyvin nopeat pelit: alle 3 minuuttia - Nopeat pelit: 3-8 minuuttia - Pikapelit: 8-25 minuuttia + Pikapelit: 8-25 minuuttia + Nopeat pelit: 8-25 minuuttia Klassiset pelit: 25 minuuttia tai enemmän Kirjeshakkipelit: yksi tai useampi päivä per siirto Taktiikkaharjoittelu diff --git a/translation/dest/site/gu-IN.xml b/translation/dest/site/gu-IN.xml index fbd4b8990f..71faf0bd49 100644 --- a/translation/dest/site/gu-IN.xml +++ b/translation/dest/site/gu-IN.xml @@ -24,6 +24,9 @@ તમે સફેદ ટુકડાઓ સાથે રમસો તમે કાળા ટુકડાઓ સાથે રમસો તમારો વારો! + આ રમત સમાપ્તિની સ્થિતિ છે, જો રમત ચાલુ હોય ત્યારે કોઈ ખેલાડી છેતરપિંડી કરે છે. રમત તરત જ સમાપ્ત થાય છે. + + અનુવાદ નિષ્ક્રિય અવાજમાં હોવો જોઈએ કારણ કે ખેલાડીઓ અને દર્શકો બંને તેને જુએ છે. કૃપા કરીને શક્ય તેટલું ટૂંકું રાખો. મધ્યમાં રાજા ત્રણ ચેક રેસ પુરી થઇ ગઇ @@ -86,12 +89,17 @@ મેટ %s ચાલ માં મેટ %s ચાલ માં + DTZ50\'\' રાઉન્ડિંગ સાથે, આગામી કેપ્ચર અથવા પ્યાદાની ચાલ સુધી અર્ધ-ચાલની સંખ્યાના આધારે ડેટાબેઝમાં આવી કોઈ રમત નથી પસંદગી મેનૂમાંથી વધુ રમતો શામેલ કરીને જોવો? રમત પ્રારંભ સંશોધક + ઓપનિંગ/એન્ડગેમ એક્સપ્લોરર %s રમત પ્રારંભ સંશોધક જીતને 50-ચાલના નિયમ દ્વારા અટકાવવામાં આવી છે 50-ચાલના નિયમ દ્વારા હાર અટકાવવામાં આવી છે + અગાઉની ભૂલથી જીત અથવા 50 ચાલ + અગાઉની ભૂલથી નુકશાન અથવા 50 ચાલ + Syzygy ટેબલબેસેસમાં DTZ મૂલ્યોના સંભવિત રાઉન્ડિંગને કારણે, છેલ્લી કેપ્ચર અથવા પ્યાદાની ચાલથી ભલામણ કરેલ ટેબલબેઝ લાઇનને અનુસરવામાં આવે તો જ જીત/હારની ખાતરી આપવામાં આવે છે. બધું સેટ છે! PGN આયાત કરો કાઢી નાખો @@ -125,6 +133,8 @@ ડ્રો માગણું ડ્રો પ્રસ્તાવો ડ્રો + પરસ્પર કરાર દ્વારા દોરો + પ્રગતિ વિના પચાસ ચાલ વર્તમાન રમતો %s રમત @@ -185,6 +195,7 @@ રદ કરો સફેદની સમય મર્યાદા પૂરી કાળાની સમય મર્યાદા પૂરી + ડ્રો ઓફર મોકલી સ્વીકારો નકારો અત્યારે રમાય છે diff --git a/translation/dest/site/hi-IN.xml b/translation/dest/site/hi-IN.xml index 79ba584588..7e2d03a8be 100644 --- a/translation/dest/site/hi-IN.xml +++ b/translation/dest/site/hi-IN.xml @@ -13,7 +13,7 @@ चैट को टोग्ल करें बातचीत हार मान लें - शाहमात + शह और मात गतिरोध (स्टेलमेट ) सफेद काला @@ -94,6 +94,8 @@ %s ओपनिंग एक्सप्लोरर जीत 50-चालन नियम से रोका गया हार 50-चाल नियम द्वारा बचाया + जीत अथ्वा 50 चाल नियम पुरानी गलती के कारण​। + हार​ अथ्वा 50 चाल नियम पुरानी गलती के कारण​। सब तैयार! PGN आयात करें हटाएँ @@ -470,7 +472,7 @@ हाँ सहायता एक नया विषय बनाएँ - विषयों की संख्या + विषय पोस्ट की संख्या आख़िरी पोस्ट देखे जाने की संख्या @@ -561,6 +563,8 @@ यह पहले से ही आपका ईमेल पता है कम से कम %s वर्ण का शब्द होना चाहिए। कम से कम %s वर्ण का शब्द होना चाहिए। + कम से कम %s + %s से ज़्यादा नही। अगर स्तर %s ऊपर निचे हो तब केवल मौजूदा वार्तालाप केवल दोस्त @@ -854,6 +858,7 @@ समकालिक खेल का विवरण क्या आप प्रतिभागियों को कुछ बताना चाहते हैं? %s अत्याधुनिक स्वरूपण के लिए उपलब्ध है। + खेल अथवा अध्ययन का URL paste करे एमबेड करने के लिये| आपके अपने क्षेत्रीय समयानुसार प्रतियोगिता वार्ता वार्ता निषेध @@ -874,5 +879,9 @@ खाते से संबंधित ईमेल पता हमने आपको लिंक के साथ एक ईमेल भेजा है। प्रतियोगिता प्रवेश कोड + प्रतिक्षा कीजीए। + %s के साथ आपका खेल समाप्त नही हुआ है। + खेल रद्द करें + हार मान ले जब तक यह गेम समाप्त नहीं हो जाता तब तक आप एक नया गेम शुरू नहीं कर सकते। diff --git a/translation/dest/site/hr-HR.xml b/translation/dest/site/hr-HR.xml index 954008eab4..8aa4292961 100644 --- a/translation/dest/site/hr-HR.xml +++ b/translation/dest/site/hr-HR.xml @@ -865,6 +865,8 @@ računalnu analizu, chat partije i URL za dijeljenje. [Klikni za prikaz e-mail adrese] Preuzmi Dobrodošli! + Postavke za trenera + Postavke za strimera Otkaži turnir Opis turnira Samo za članove tima diff --git a/translation/dest/site/hy-AM.xml b/translation/dest/site/hy-AM.xml index fc1ca4ab05..76df9da0b0 100644 --- a/translation/dest/site/hy-AM.xml +++ b/translation/dest/site/hy-AM.xml @@ -158,7 +158,7 @@ Խաղացողներ Ընկերներ Հաղորդագրություններ - Այսօր + Այսօր Երեկ Րոպեներ ամեն կողմի համար Տարբերակ @@ -318,6 +318,8 @@ Դուք պետք է խաղաք ևս %s վարկանիշային խաղ Ձեր վարկանիշը %s-ում դեռևս վավեր չէ + Ձեր ամենշաբաթյա վարկանիշը %1$sում (%2$s) չափազանց բարձր է + Ձեր վարկանիշը %1$sին (%2$s) մասնակցելու համար բավարար չէ Վարկանիշը ≥ %1$s %2$s-ում Վարկանիշը ≤ %1$s %2$s-ում Դուք պետք է լինեք %s ակումբի անդամ @@ -426,9 +428,9 @@ Հանրաճանաչ սկզբնախաղեր Վերջնախաղային դիրքեր Շախմատ 960-ի սկզբնական դիրք՝ %s - Սկզբնական դիրք + Սկզբնական դիրք Մաքրել տախտակը - Բեռնել դիրք + Բեռնել դիրք Անձնական Տեղեկացնել %s մոդերատորներին Մասնակցային էջը լրացված է %s-ով @@ -447,7 +449,7 @@ Նախորդները Lichess TV-ով Առցանց խաղացողներ Ակտիվ խաղացողներ - Զգուշացում. խաղը վարկանիշային է, սակայն առանց ժամանակի + Զգուշացում. խաղը վարկանիշային է, սակայն առանց ժամանակի Հաջողված է Ընթանում է %s խաղ @@ -512,7 +514,7 @@ Դանդաղ խաղերում Միշտ Երբեք - %1$s որոշել է մասնակցել %2$s-ին + %1$s որոշել է մասնակցել %2$s-ին Հաղթանակ Պարտություն %1$s-ն ընդդեմ %2$s-ի %3$s-ում diff --git a/translation/dest/site/id-ID.xml b/translation/dest/site/id-ID.xml index 467521531a..6bbf41a333 100644 --- a/translation/dest/site/id-ID.xml +++ b/translation/dest/site/id-ID.xml @@ -152,7 +152,7 @@ Pemain Teman Diskusi - Hari ini + Hari ini Kemarin Menit untuk tiap pihak Variasi permainan @@ -402,9 +402,9 @@ Pembuka populer Posisi akhir permainan Posisi awal Catur960: %s - Posisi Awal + Posisi Awal Bersihkan papan - Panggil posisi + Panggil posisi Pribadi Lapor %s ke moderator Kelengkapan profil %s @@ -423,7 +423,7 @@ Sebelumnya di Lichess TV Pemain online Pemain aktif - Awas, permainan ini pake rating namun tanpa pake waktu! + Awas, permainan ini pake rating namun tanpa pake waktu! Berhasil %s pertandingan sedang berlangsung @@ -491,7 +491,7 @@ Pada catur klasik Selalu Tidak pernah - %1$s berkompetisi dalam %2$s + %1$s berkompetisi dalam %2$s Berhasil Kalah %1$s melawan %2$s dalam permainan %3$s @@ -524,10 +524,10 @@ Alamat e-mail ini tidak dapat diterma. Pastikan kembali e-mail Anda, lalu coba lagi. Alamat e-mail tidak valid atau sudah dipakai Ini sudah menjadi alamat e-mail Anda - Panjang minimum adalah %s - Panjang maksimum adalah %s - Harus lebih besar atau sama dengan %s - Harus kurang atau sama dengan %s + Panjang minimum adalah %s + Panjang maksimum adalah %s + Harus lebih besar atau sama dengan %s + Harus kurang atau sama dengan %s Jika ratingnya ± %s Khusus percakapan yang ada Hanya teman @@ -611,7 +611,7 @@ Unduh mentah Unduh permainan yang telah dimasukkan Napak tilas - Anda juga bisa menggunakan tombol gulung untuk bergerak dalam permainan + Anda juga bisa menggunakan scroll untuk bergerak dalam permainan. Letakkan mouse diatas variasi komputer untuk meninjau hal tersebut. Tekan shift+klik atau klik-kanan untuk menggambar lingkaran dan panah di papan. Memperbolehkan pemain lain mengirim pesan untuk Anda @@ -647,7 +647,7 @@ %1$s tuan rumah %2$s %1$s bergabung %2$s %1$s suka %2$s - Pencarian lawan dengan cepat + Pencarian cepat Ruang Lobi Tanpa Nama Skor Anda: %s @@ -770,7 +770,7 @@ Anda belum bisa mengirim pos di forum ini. Mainkan beberapa permainan! Berlangganan Berhenti berlangganan - membahas anda di \"%1$s\". + membahas anda di \"%1$s\". %1$s membahas anda di \"%2$s\". mengundang anda ke \"%1$s\". %1$s mengundang ke \"%2$s\". diff --git a/translation/dest/site/kk-KZ.xml b/translation/dest/site/kk-KZ.xml index 002a749a27..063f69bec9 100644 --- a/translation/dest/site/kk-KZ.xml +++ b/translation/dest/site/kk-KZ.xml @@ -231,9 +231,9 @@ Классикалық Шектеусіз Түрі - Қалыпты + Жай Бағаланатын - Қалыпты + Кәдімгі Бағаланатын Бұл ойын бағаланады Қайта ойнау @@ -368,6 +368,8 @@ Серік болу Серіксіз Серік болмау + %s серік болу + %s серік болмау Бұғаттау Бұғатталған Бұғаттан шығару @@ -515,7 +517,7 @@ Дыбыстар Жоқ Тез - Қалыпты + Орташа Баяу Тақта бетінде Тақта сыртында @@ -553,6 +555,14 @@ Құралдар Қосылғыш Бұл жолды толтырыңыз + Бұл пошта мекенжайы қате + Бұл пошта мекенжайы қабылданбайды. Оны тексеріп, қайта көріңіз. + Пошта мекенжайы қате немесе бос емес + Бұл сіздің бұрыңғы поштаңыз + Кемінде %s таңба болу керек + Ең көбі %s таңба болу керек + Кемінде %s болу керек + Ең көбі %s болу керек Егер рейтингі ± %s болса Тек бар әңгімелесуден Достар ғана @@ -591,10 +601,10 @@ Фишер 50 қарсыластың 47-сін жеңіп, 2-мен тең ойнап, 1-нен жеңілді. Бұл түсінік шынайы өмірден алынған: ойында бас ойыншы болады, ол тақталарды аралап, бір-бір жүрістен жасап отырады. Көпшілікке қарсы ойын басталысымен, әр ойыншы ақ тастар иесі – бас ойыншымен жарысады. Барлық ішкі ойындар аяқталғанда, жалпы ойын да аяқталады. - Көпшілікке қарсы ойын – әрқашанда қалыпты. Қайта ойнау, жүрісті қайтару, уақыт қосу дегендер болмайды. + Көпшілікке қарсы ойын – әрқашанда жай ойын. Қайта ойнау, жүрісті қайтару, уақыт қосу дегендер болмайды. Құру Сіз көпшілікке қарсы ойынды құрғанда, бір мезгілде бірнеше қарсыласпен ойнайсыз. - Егер сіз бірнеше шахмат түрін таңдасаңыз, әр ойыншы соның арасынан өзіне лайық біреуін таңдайды. + Егер сіз бірнеше шахмат түрін таңдасаңыз, әр ойыншы соның арасынан біреуін таңдайды. Фишер Сағатын баптау. Қарсыластарыңыз көп болса, уақыт та көп қажет. Көпшілікке қарсы ойынды жеңілдету үшін өзіңізге қосымша уақыт қоса аласыз. Бас ойыншыға қосымша уақыт @@ -700,7 +710,7 @@ Бапкерлер Қате PGN Қате FEN - Қалыпты + Басқа Хабарлама Рейтинг: %s @@ -740,7 +750,7 @@ Қайта жасау Ақтардың қателерін қарау Қаралардың қателерін қарау - Артықшылық + Басымдылық %s секунд %s секунд @@ -782,10 +792,10 @@ Рапид Классикалық Лезде ойындар: 30 секундтан кем - Аса жылдам ойындар: 3 минуттан кем + Аса жылдам ойындар: 3 минуттан аз Жылдам ойындар: 3-тен 8 минутке дейін Рапид ойындары: 8-ден 25 минутке дейін - Классикалық ойындар: 25 минут және одан артық + Классикалық ойындар: 25 минут әрі одан көп Хат алмасып ойнау: бір жүріске бір не бірнеше күн Шахмат тактикасын үйретуші Ескерту @@ -836,8 +846,8 @@ Тек атаққа ие ойыншылар Жарысқа қосу үшін ресми атағын талап ету Әр ойынды нақты бір күйден бастау үшін FEN еңгізіңіз. -Бұл тек қалыпты ойындарда ғана жұмыс істейді, басқа шахмат түрлерінде емес. -Күйдің FEN-ін құру үшін %s қолданыңыз, кейін осында қойыңыз. +Бұл тек классикалық ойындарда ғана жұмыс істейді, басқа шахмат түрлерінде емес. +Күйдің FEN-ін құру үшін %s қолданыңыз, кейін осында еңгізіңіз. Ойынды кәдімгі бастапқы күйден бастау үшін бос қалдыра салыңыз. Көпшілікке қарсы ойынды болдырмау Әрқашан бас ойыншы түсінен тәуелді diff --git a/translation/dest/site/lb-LU.xml b/translation/dest/site/lb-LU.xml index 490d405d06..5cd52f8746 100644 --- a/translation/dest/site/lb-LU.xml +++ b/translation/dest/site/lb-LU.xml @@ -163,7 +163,7 @@ Spiller Frënn Konversatiounen - Haut + Haut Gëschter Minutten pro Säit Variant @@ -435,9 +435,9 @@ Beléift Eröffnungen Endspill Positiounen Chess960 Start Positioun: %s - Start Positioun + Start Positioun Briet opraumen - Positioun lueden + Positioun lueden Privat %s den Moderatoren mellen Profil vollstänneg zu: %s @@ -456,7 +456,7 @@ Virdrun op Lichess TV Online Spiller Aktiv Spiller - Pass op, d\'Partie ass gewäert mee huet keng Auer! + Pass op, d\'Partie ass gewäert mee huet keng Auer! Succès %s Partie amgaang @@ -525,7 +525,7 @@ An luesen Partien Ëmmer Nie - %1$s hëlt deel bei %2$s + %1$s hëlt deel bei %2$s Victoire Defaite %1$s vs %2$s an %3$s @@ -550,7 +550,7 @@ Auer Géigner Léieren - Léieren + Etüden Üben Gemeinschaft Tools @@ -560,10 +560,10 @@ Dës Email Adress ass net akzeptabel. Iwwerpréif se wannechgelift an probéier nach eng Kéier. Email Adress ongëlteg oder schon benotzt Dat ass schon deng Email Adress - Mindestens %s Zeechen - Maximal %s Zeechen - Muss mindestens %s sinn - Däerf maximal %s sinn + Mindestens %s Zeechen + Maximal %s Zeechen + Muss mindestens %s sinn + Däerf maximal %s sinn Falls Wäertung ± %s Just lafend Konversatiounen Just Frënn @@ -812,7 +812,7 @@ Du kanns nach net am Forum schreiwen. Spill puer Partien! Abonéieren Desabonéiren - huet dech an \"%1$s\" erwähnt. + huet dech an \"%1$s\" erwähnt. %1$s huet dech an \"%2$s\" erwähnt. huet dech zu \"%1$s\" invitéiert. %1$s huet dech zu \"%2$s\" invitéiert. diff --git a/translation/dest/site/ml-IN.xml b/translation/dest/site/ml-IN.xml index bc290c49bc..c614944ce5 100644 --- a/translation/dest/site/ml-IN.xml +++ b/translation/dest/site/ml-IN.xml @@ -24,6 +24,7 @@ നിങ്ങൾ വെള്ള കരുകൾ വെച്ച് കളിക്കുന്നു നിങ്ങൾ കറുപ്പ് കരുകൾ വെച്ച് കളിക്കുന്നു നിങ്ങളുടെ കളി! + കള്ളത്തരം കണ്ടെത്തി കിംഗ് ഇൻ ദി സെന്റർ ഗെയിം മൂനു ചെക്ക് ഗെയിം ഓട്ടം അവസാനിച്ചിരിക്കുന്നു @@ -89,6 +90,7 @@ ഒരു ഗെയിമും കണ്ടെത്താൻ സാധിച്ചില്ല പരിഗണന മെനുവിൽ നിന്ന് കൂടുതൽ ഗെയിമുകൾ ഉൾപ്പെടുത്താണോ? എക്സ്പ്ലോററും ടേബിൾ ബേസും തുറക്കാം + ഓപ്പണിങ്ങ്/എൻഡ്ഗെയിം എക്‌സ്‌പ്ലോറർ %s ഓപ്പണിങ് എക്സ്പ്ലോറർ 50 നീക്കങ്ങളുടെ നിയമം പ്രകാരം ജയം തടയപ്പെട്ടു 50 നീക്കങ്ങളുടെ നിയമം പ്രകാരം തോൽവി തടയപ്പെട്ടു @@ -428,6 +430,7 @@ കളിക്കളം സജ്ജമാക്കുക പ്രചാരത്തിലുള്ള ഓപ്പണിങ്ങുകൾ ഏൻഡ് ഗെയിം പൊസിഷനുകൾ + ചതുരംഗം960 ആരംഭ സ്ഥാനം: %s പ്രാരംഭ സ്ഥാനം പലക വ്യക്തമാക്കുക സ്ഥാനത്തു നിന്ന് @@ -550,6 +553,7 @@ കയറ്റം ഈ ഭാഗം ആവശ്യമാണ് ഈ ഇമെയിൽ അഡ്രസ് സാധുവല്ല + ഈ ഇമെയിൽ വിലാസം സ്വീകാര്യമല്ല. പരിശോധിച്ചുറപ്പാക്കി വീണ്ടും ശ്രമിക്കുക. ± %s ക്രമത്തിലാണോ നിലവിലുള്ള സംഭാഷങ്ങൾ മാത്രം സുഹൃത്തുക്കൾക്കു മാത്രം diff --git a/translation/dest/site/ms-MY.xml b/translation/dest/site/ms-MY.xml index b05258a63d..1f669455ea 100644 --- a/translation/dest/site/ms-MY.xml +++ b/translation/dest/site/ms-MY.xml @@ -51,6 +51,7 @@ Minta analisis komputer Analisis komputer Analisis komputer ada + Analisis komputer ditutup Analisis papan Depth %s Menggunakan server analisis @@ -84,6 +85,7 @@ Mate dalam %s separuh-pergerakan + DTZ50\" yang dibundar, mengikut jumlah pegerakkan separuh sehingga sebuah bidak dimakan atau pegerakkan pion seterusnya Tiada perlawanan dijumpai Mungki masukkan lebih banyak perlawanan daripada pilihan menu? Membuka explorer @@ -138,7 +140,7 @@ Senarai pemain Senarai rakan Senarai perbualan - Hari ini + Hari ini Semalam Minit bagi setiap bahagian Variasi @@ -350,7 +352,7 @@ Buah putih menang Menang Hitam Seri - Posisi permulaan + Posisi permulaan Pautan media sosial Pemain aktif Berjaya diff --git a/translation/dest/site/ne-NP.xml b/translation/dest/site/ne-NP.xml index 0441e58ca8..dbaa0d6f7e 100644 --- a/translation/dest/site/ne-NP.xml +++ b/translation/dest/site/ne-NP.xml @@ -24,6 +24,7 @@ तपाईं सेतो खेल्नुहुनेछ तपाईं कालो खेल्नुहुनेछ तपाईंको पालो! + बेइमानी पत्ता पर्यो राजा बिचमा तिनवटा चेकहरु दौड सकियो @@ -49,6 +50,7 @@ कम्प्युटर विश्लेषण आग्रह गर्नुहोस् कम्प्युटर विश्लेषण कम्प्युटर विश्लेषण उपलब्ध छ + कम्प्युटर विश्लेषण बन्द छ विश्लेषण बोर्ड गहिराइ %s सर्भरको विश्लेषण प्रयोग गरिदैं छ @@ -140,7 +142,7 @@ खेलाडीहरू साथीहरू वार्तालाप - आज + आज हिजो एक पक्षको मिनट परिमार्जन @@ -257,6 +259,7 @@ %s सेकेण्ड दिनु %s सेकेण्ड दिनु + यस खाताले Lichess Terms of Service को उल्लङ्घन गरेको छ शुरुका चाल अन्वेषक & टेबलबेस चाल फिर्ता चाल फिर्ता लिने प्रस्ताव गरौं @@ -405,9 +408,9 @@ बोर्ड सम्पादक बोर्ड सेट गरौं लोकप्रिय सुरुवाती चालहरु - शुरुवाती अवस्था + शुरुवाती अवस्था बोर्ड खालि गरौं - पोजिसन लोड गरौं + पोजिसन लोड गरौं नीजि %s माथी उजुरी गरौं प्रोफाइल समापन: %s @@ -419,13 +422,14 @@ शुभनाम थर जीवनी + देश अथवा झण्डा धन्यवाद! सामाजिक सञ्जाल लिङ्क पङ्तिमा नोटेशन लीचेस टिभीमा यसअघि अनलाइन खेलाडीहरु कृयाशील खेलाडीहरु - सावधान, खेल रेटिङवाला हो तर कुनै घडी छैन! + सावधान, खेल रेटिङवाला हो तर कुनै घडी छैन! सफलता %s खेल जारी @@ -475,6 +479,7 @@ घडीको शुरु समय हरेक चालमा थप हुने समय गोपनियता + गोप्यता नीति अरु खेलाडीलाई पछ्याउन दिने अरु खेलाडी लाई खेलको निम्तो दिन दिने अरु खेलाडीलाई पाठ मा आमन्त्रण गर्न दिने @@ -488,7 +493,7 @@ ढिलो खेलहरुमा सधैँ नाइँ - %1$s %2$s मा भाग लिनुहुन्छ + %1$s %2$s मा भाग लिनुहुन्छ विजय हार %1$s बनाम %2$s: %3$s मा @@ -754,5 +759,9 @@ प्रायः सोधिने प्रश्नहरूमा चोरी वा खराब ब्यहोराको उजुरी गर्नुपरेमा %1$s रिपोर्ट फारम प्रयोग गर्नुहोस् + मद्दतका लागि अनुरोध गर्न, %1$s + सम्पर्क पेजबाट प्रयास गर्नुहोस् + यो विषय सङ्गृहीत छ र यसलाई जवाफ दिन सकिँदैन। + %1$s टोली समय सक्किन लाग्यो! diff --git a/translation/dest/site/nl-NL.xml b/translation/dest/site/nl-NL.xml index 5fa6a74dfa..7fed709167 100644 --- a/translation/dest/site/nl-NL.xml +++ b/translation/dest/site/nl-NL.xml @@ -163,7 +163,7 @@ Spelers Vrienden Discussies - Vandaag + Vandaag Gisteren Minuten per speler Variant @@ -435,9 +435,9 @@ Populaire openingen Eindspel posities Beginstelling Chess960: %s - Beginstelling + Beginstelling Maak het bord leeg - Laad een stelling + Laad een stelling Privé Rapporteer %s aan de toezichthouders Voltooiing profiel: %s @@ -456,7 +456,7 @@ Voorheen op Lichess TV Online spelers Actieve spelers - Let op, de partij is met rating maar heeft geen klok! + Let op, de partij is met rating maar heeft geen klok! Succes %s partij bezig @@ -525,7 +525,7 @@ Alleen bij langzame partijen Altijd Nooit - %1$s neemt deel aan %2$s + %1$s neemt deel aan %2$s Overwinning Nederlaag %1$s vs. %2$s in %3$s @@ -560,10 +560,10 @@ Dit e-mailadres is niet acceptabel. Controleer het nogmaals en probeer het opnieuw. E-mailadres ongeldig of al in gebruik Dit is al uw e-mailadres - Minimumlengte is %s tekens - Maximumlengte is %s tekens - Moet groter dan of gelijk zijn aan %s - Moet kleiner dan of gelijk zijn aan %s + Minimumlengte is %s tekens + Maximumlengte is %s tekens + Moet groter dan of gelijk zijn aan %s + Moet kleiner dan of gelijk zijn aan %s Indien de rating ± %s is Alleen bestaande gesprekken Alleen vrienden @@ -812,7 +812,7 @@ Je kunt nog geen post in de forums plaatsen. Speel eerst een paar partijen! Abonneren Abonnement opzeggen - heeft je genoemd in \"%1$s\". + heeft je genoemd in \"%1$s\". %1$s heeft je genoemd in \"%2$s\". heeft je uitgenodigd voor \"%1$s\". %1$s heeft je uitgenodigd voor \"%2$s\". diff --git a/translation/dest/site/or-IN.xml b/translation/dest/site/or-IN.xml index f06c354397..6d78d9f01a 100644 --- a/translation/dest/site/or-IN.xml +++ b/translation/dest/site/or-IN.xml @@ -21,8 +21,8 @@ ଏକ ଖେଳ ସୃଷ୍ଟି କରନ୍ତୁ ଧଳା ବିଜୟୀ ଅଟେ କଳା ବିଜୟୀ ଅଟେ - ଆପଣ ଧଳା ଖଣ୍ଡଗୁଡ଼ିକ ଖେଳୁଛନ୍ତି - ଆପଣ କଳା ଖଣ୍ଡଗୁଡ଼ିକ ଖେଳୁଛନ୍ତି + ଆପଣ ଧଳା ଗୋଟିଗୁଡ଼ିକ ଖେଳୁଛନ୍ତି + ଆପଣ କଳା ଗୋଟିଗୁଡ଼ିକ ଖେଳୁଛନ୍ତି ଏହା ଆପଣଙ୍କର ପାଳି! କେନ୍ଦ୍ରରେ ରାଜା | ତିନି ଚେକ୍ @@ -33,7 +33,11 @@ ଖେଳରେ ଯୋଗ ଦିଅନ୍ତୁ ଧଳାର ପାଳି କଳାର ପାଳି - ତୁମର ପ୍ରତିଦ୍ୱନ୍ଦୀ ଖେଳ ଛାଡିଥିଲେ | ଆପଣ ବିଜୟ ଦାବି କରିପାରିବେ, ଖେଳକୁ ଡ୍ର ବୋଲି କହିପାରିବେ କିମ୍ବା ଅପେକ୍ଷା କରିପାରିବେ | + + ଆପଣଙ୍କ ପ୍ରତିଦ୍ୱନ୍ଦ୍ୱୀ ଖେଳ ଛାଡ଼ିଛନ୍ତି। ଆପଣ %s ସେକେଣ୍ଡରେ ବିଜୟ ଦାବି କରିପାରିବେ। + ଆପଣଙ୍କ ପ୍ରତିଦ୍ୱନ୍ଦ୍ୱୀ ଖେଳ ଛାଡ଼ିଛନ୍ତି। ଆପଣ %s ସେକେଣ୍ଡରେ ବିଜୟ ଦାବି କରିପାରିବେ। + + ଆପଣଙ୍କ ପ୍ରତିଦ୍ୱନ୍ଦ୍ୱୀ ଖେଳ ଛାଡ଼ିଛନ୍ତି। ଆପଣ ବିଜୟ ଦାବି କରିପାରିବେ, ଖେଳକୁ ଡ୍ର ବୋଲି କହିପାରିବେ କିମ୍ବା ଅପେକ୍ଷା କରିପାରିବେ। ବିଜୟ ଦାବି କରନ୍ତୁ କଲ୍ ଡ୍ର ଦୟାକରି ଚାଟରେ ଭଲ ହୁଅନ୍ତୁ! @@ -142,17 +146,17 @@ ଉପଭୋକ୍ତାଙ୍କ ନାମ ଉପଭୋକ୍ତା ନାମ କିମ୍ୱା ଇମେଲ୍ ଉପଭୋକ୍ତା ନାମ ପରିବର୍ତ୍ତନ କରନ୍ତୁ - ପାସ୍ଓ୍ୱାର୍ଡ଼ - ପାସ୍ଓ୍ୱାର୍ଡ଼ ପରିବର୍ତ୍ତନ କରନ୍ତୁ + ପାସ୍‍ୱାର୍ଡ଼ + ପାସ୍‍ୱାର୍ଡ଼ ବଦଳାନ୍ତୁ ଇମେଲ୍ ପରିବର୍ତ୍ତନ କରନ୍ତୁ ଇମେଲ୍ - ପାସ୍ଓ୍ୱାର୍ଡ଼ ପୁନଃସେଟ୍ କର - ପାସ୍ଓ୍ୱାର୍ଡ଼ ଭୁଲିଯାଇଛନ୍ତି କି? + ପାସ୍‍ୱାର୍ଡ଼ ପୁନଃସେଟ୍ କର + ପାସ୍‍ୱାର୍ଡ଼ ଭୁଲିଯାଇଛନ୍ତି କି? %sଟିଏ ପ୍ରହେଳିକା %sଟି ପ୍ରହେଳିକା - ଖେଳାଯାଇଥିବା ଖେଳଗୁଡ଼ିକ + ଖେଳାଯାଇଥିବା ଖେଳଗୁଡ଼ିକ ବର୍ତ୍ତମାନ ଖେଳୁଛନ୍ତି ଏହି ଆକାଉଣ୍ଟ Lichess ସେବା ସର୍ତ୍ତାବଳୀର ଉଲ୍ଲଂଘନ କରିଛି @@ -196,16 +200,17 @@ ଉପଭୋକ୍ତା କାରଣ ମାମଲା କ\'ଣ? - ଅବୈଧ ଉପଭୋକ୍ତାଙ୍କ ନାମ କିମ୍ୱା ପାସ୍ଓ୍ୱାର୍ଡ଼ - ଭୁଲ୍ ପାସ୍ଓ୍ୱାର୍ଡ଼ - ଏବେକାର ପାସ୍ଓ୍ୱାର୍ଡ଼ - ନୁତନ ପାସ୍ଓ୍ୱାର୍ଡ଼ - ନୂତନ ପାସ୍ଓ୍ୱାର୍ଡ଼ (ପୁନଃ) - ନୂତନ ପାସ୍ଓ୍ୱାର୍ଡ଼ଗୁଡ଼ିକ ମେଳ ଖାଉ ନାହିଁ + ଅବୈଧ ଉପଭୋକ୍ତାଙ୍କ ନାମ କିମ୍ୱା ପାସ୍‍ୱାର୍ଡ଼ + ଭୁଲ୍ ପାସ୍‍ୱାର୍ଡ଼ + ଏବେକାର ପାସ୍‍ୱାର୍ଡ଼ + ନୂଆ ପାସ୍‍ୱାର୍ଡ଼ + ନୂଆ ପାସ୍‍ୱାର୍ଡ଼ (ପୁନଃ) + ନୂତନ ପାସ୍‍ୱାର୍ଡ଼ଗୁଡ଼ିକ ମେଳ ଖାଉ ନାହିଁ ଗୋପନୀୟତା ଗୋପନୀୟତା ନୀତି ଅନ୍ୟ ଖେଳାଳିମାନଙ୍କୁ ଆପଣଙ୍କୁ ଅଧ୍ୟୟନ ପାଇଁ ଆମନ୍ତ୍ରଣ କରିବାକୁ ଦିଅନ୍ତୁ ଧ୍ଵନି + %1$s %2$sରେ ପ୍ରତିଦ୍ୱନ୍ଦ୍ୱିତା କରନ୍ତି ବିଜୟ ସମୟରେଖା ଆରମ୍ଭ: @@ -223,7 +228,7 @@ କେବଳ ବନ୍ଧୁଗଣ ସେବାର ସର୍ତ୍ତାବଳୀ ସୃଷ୍ଟି କରନ୍ତୁ - ଆମେ %s କୁ ଏକ ଇମେଲ୍ ପଠାଇଛୁ। ଆପଣଙ୍କ ପାସ୍ଓ୍ୱାର୍ଡ଼ ପୁନଃସେଟ୍ କରିବାକୁ ଇମେଲରେ ଥିବା ଲିଙ୍କକୁ କ୍ଲିକ୍ କରନ୍ତୁ। + ଆମେ %s କୁ ଏକ ଇମେଲ୍ ପଠାଇଛୁ। ଆପଣଙ୍କ ପାସ୍‍ୱାର୍ଡ଼ ପୁନଃସେଟ୍ କରିବାକୁ ଇମେଲରେ ଥିବା ଲିଙ୍କକୁ କ୍ଲିକ୍ କରନ୍ତୁ। ଆମର %s ବିଷୟରେ ପଢ଼ନ୍ତୁ। ସୁରକ୍ଷା ସମାଧାନ ଦେଖନ୍ତୁ @@ -232,6 +237,7 @@ %sଟି ଭାଷାରେ ଉପଲବ୍ଧ! ବେନାମୀ + ଆପଣଙ୍କ ସ୍କୋର୍: %s ଭାଷା ହାଲୁକା ଗାଢ଼ @@ -242,14 +248,16 @@ କ୍ଷମା କରନ୍ତୁ :( କାହିଁକି? ଆମେ ସମସ୍ତଙ୍କ ପାଇଁ ଏକ ସୁଖଦ ଚେସ୍ ଅଭିଜ୍ଞତା ପ୍ରଦାନ କରିବାକୁ ଲକ୍ଷ୍ୟ ରଖିଛୁ। - ଏହାକୁ କିପରି ଏଡ଼ାଇ ହେବ? + ଏହାକୁ କିପରି ଏଡ଼ାଇ ହେବ? ପଢ଼ିବା ପାଇଁ ଧନ୍ୟବାଦ! + ଆଜୀବନ ସ୍କୋର୍ ମୁଁ ସହମତ ଯେ ମୁଁ ସମସ୍ତ Lichess ନୀତି ଅନୁସରଣ କରିବି। ସମ୍ପାଦନା ଶାସ୍ତ୍ରୀୟ ଚେସ୍ କୌଶଳ ପ୍ରଶିକ୍ଷକ ସଦସ୍ୟତା ନିଅନ୍ତୁ ସଦସ୍ୟତା ରଦ୍ଦ କରନ୍ତୁ + ଆପଣଙ୍କୁ \"%1$s\"ରେ ଉଲ୍ଲେଖ କରିଛନ୍ତି। ନୂଆ ବିଚାରାଧୀନ ସମୀକ୍ଷା ଏହାକୁ ଗ୍ରନ୍ଥନ କରିବା ପାଇଁ ଏକ ଖେଳ URL କିମ୍ବା ଏକ ଅଧ୍ୟାୟ URL ପେଷ୍ଟ କରନ୍ତୁ। କେବଳ ଦଳର ସଦସ୍ୟଗଣ diff --git a/translation/dest/site/os-SE.xml b/translation/dest/site/os-SE.xml index fe14c46d09..464ca20e1c 100644 --- a/translation/dest/site/os-SE.xml +++ b/translation/dest/site/os-SE.xml @@ -122,7 +122,7 @@ Рæстæг цыдæн Фæйнæг разилын - Æртæхатты позицийы фæлхатын + Æртывæр позицийы фæлхатын Æрдомын хæрæгсаст Бахъарын хæрæгсаст Хæрæгсаст @@ -152,7 +152,7 @@ Хъазджытæ Æмбалтæ Ныхæстæ - Абон + Абон Знон Минутты партийæн Вариант @@ -420,9 +420,9 @@ Фæйнæджы редактор Æрæвæрын позици Адæмы уарзон райдайæнтæ - Райдайæн позици + Райдайæн позици Фæйнæг асыгъдæг кæнын - Позици бавгæнын + Позици бавгæнын Æхгæд Фехъусын кæнын %s-ы тыххæй модератортæн Профилы дзагдзинад: %s @@ -440,7 +440,7 @@ Раздæр Lichess TV-йы Онлайн хъазджытæ Разæнгард хъазджытæ - Хъус бадар, ацы хъазт у рейтингимæ, фæлæ нæй йын рæстæджы цæстдарды! + Хъус бадар, ацы хъазт у рейтингимæ, фæлæ нæй йын рæстæджы цæстдарды! Рауади %s парти @@ -509,7 +509,7 @@ Сындæг хъæзтыты Æдзух Никуы - %1$s сфæнд кодта %2$s-ы хайад исын + %1$s сфæнд кодта %2$s-ы хайад исын Уæлахиз Дæрæн %1$s %2$s-йы ныхмæ %3$s-ы diff --git a/translation/dest/site/sc-IT.xml b/translation/dest/site/sc-IT.xml index 3ea04e700d..812c6eb280 100644 --- a/translation/dest/site/sc-IT.xml +++ b/translation/dest/site/sc-IT.xml @@ -1,2 +1,16 @@ - + + Gioga con unu amigu + Gioga chin su elaboradore + Para inbidai calicunu dona issu custu URL + As perdiu + Ispettende unu enemigu + Ispettende + A tue + %1$s difficultade %2$s + Difficultade + Fortza + Chat + Arrendidi + Scaccu maccu + diff --git a/translation/dest/site/si-LK.xml b/translation/dest/site/si-LK.xml index 5d06a806d3..ef712e568f 100644 --- a/translation/dest/site/si-LK.xml +++ b/translation/dest/site/si-LK.xml @@ -87,6 +87,7 @@ ඇදීම් %sකින් චෙක්මේට් ඇදීම් වාර %sකින් චෙක්මේට් + DTZ50\'\' with rounding, based on number of half-moves until next capture or pawn move සෙල්ලමක් හමුවුන්නෑ මනාප ලැයිස්තුවේ තවත් සෙල්ලම් තිබිය හැක? පටන්ගැනීම් ගවේෂකය @@ -151,7 +152,7 @@ ක්‍රීඩකයන් යහළුවන් සංවාද - අද + අද ඊයේ එක් පැත්තකට මිනිත්තු වර්ගය diff --git a/translation/dest/site/te-IN.xml b/translation/dest/site/te-IN.xml index 4b879d8a8f..30535ec188 100644 --- a/translation/dest/site/te-IN.xml +++ b/translation/dest/site/te-IN.xml @@ -147,7 +147,7 @@ ఆటగాళ్లు స్నేహితులు సంభాషణలు - ఈరోజు + ఈరోజు నిన్న గడువు రకం @@ -323,6 +323,8 @@ Translation results సగటు రేటింగ్ స్థానం ఫిల్టర్ గేమ్‌లు + సేవ్ + లీడర్‌బోర్డు గ్రాఫ్ %s నిమిషం కన్నా తక్కువ @@ -367,10 +369,107 @@ Translation results బోర్డు ఎడిటర్ బోర్డు సెట్ చేయండి జనాదరణ పొందిన ఓపెనింగ్స్ - ప్రారంభ స్థానం + ప్రారంభ స్థానం క్లియర్ బోర్డు - స్థానం లోడ్ + స్థానం లోడ్ ప్రైవేట్ మోడరేటర్లకు %s ని నివేదించండి ప్రొఫైల్ పూర్తయింది:%s + ప్రొఫైల్ + ప్రొఫైల్ మార్చాలి + మీ దేశం లేదా జెండా + ధన్యవాదాలు! + సోషల్ మీడియా లింక్‌లు + Inline notation + గతంలో Lichess TVలో + ఆన్‌లైన్ ప్లేయర్‌లు + Active ఆటగాళ్ళు + జాగ్రత్త, గేమ్ రేట్ చేయబడింది కానీ గడియారం లేదు! + విజయం + + %s ఆటలు ఆడుతున్నారు + %s ఆటలు ఆడుతున్నారు + + కదిలించిన తర్వాత దానంతట అదే తదుపరి గేమ్‌కు వెళ్లాలి + ఆటో స్విచ్ + పజిల్స్ + టోర్నమెంట్ విజేతలు + పేరు + అతడు/ఆమె చేసిన తప్పేంటి + ప్రైవేట్ సమాచారం + టీమ్ మెంబర్స్ మాత్రమే చూడగలిగే సమాచారం. సెట్ చేస్తే, బృంద సభ్యుల కోసం పబ్లిక్ సమాచారాన్ని భర్తీ చేస్తుంది. + లేదు + అవును + సహాయం: + కొత్త అంశాన్ని సృష్టించండి + అంశాలు + పోస్ట్‌లు + చివరి పోస్ట్ + వీక్షకులు + సమాధానాలు + ఈ అంశానికి సమాధానం ఇవ్వండి + సమాధానం + సందేశం + కొత్త అంశాన్ని సృష్టించండి + రిపోర్ట్ యూజర్ + వినియోగదారుడు + కారణం + మీరు ఎందుకు రిపోర్టు చేస్తున్నారు? + మోసం + అవమానం + ఎగతాళి చేయండి + రేటింగ్ మానిప్యులేషన్ + ఇతర + గేమ్/గేమ్‌ల లింక్‌ను పంపండి మరియు ఈ వినియోగదారు ప్రవర్తనలో తప్పు ఏమిటో వివరించండి. \"వారు మోసం చేసారు\" అని చెప్పకండి, కానీ మీరు ఈ నిర్ణయానికి ఎలా వచ్చారో మాకు చెప్పండి. మీ నివేదికను ఆంగ్లంలో వ్రాస్తే వేగంగా ప్రాసెస్ చేయబడుతుంది. + దయచేసి కనీసం మోసం చేసిన ఆట ఒక లింక్‌నైనౌ అందించండి. + %s చే + ఈ అంశం ఇప్పుడు మూసివేయబడింది. + బ్లాగ్ + గమనిక + మీ ప్రైవేట్ గమనికలను ఇక్కడ టైప్ చేయండి + వినియోగదారు పేరు లేదా పాస్‌వర్డ్ చెల్లదు + తప్పు పాస్‌వర్డ్ + చెల్లని ప్రమాణీకరణ కోడ్ + నాకు ఒక లింక్ ఇమెయిల్ చేయండి + ప్రస్తుత పాస్వర్డ్ + కొత్త పాస్వర్డ్ + కొత్త పాస్వర్డ్ (మళ్లీ టైప్ చేయండి) + కొత్త పాస్‌వర్డ్‌లు ఒకేలా లేవు + పాస్వర్డ్ బలం + గడియారం ప్రారంభ సమయం + గడియారం పెంపు + పరికరాలు + ఈ సమాచారం అవసరం + కాస్లింగ్ + తెలుపు O-O + నలుపు O-O + + %s ఫోరమ్ పోస్ట్ + %s ఫోరమ్ పోస్ట్‌లు + + ఆడుతూ గడిపిన సమయం: %s + ఆటలు చూడండి + టీవీలో ప్రదర్శించబడిన సమయం: %s + వీక్షించండి + వీడియో లైబ్రరీ + స్ట్రీమర్లు + మొబైల్ యాప్ + Webmasters + మా గురించి + %s గురించి + %1$s అనేది ఉచితమైన (%2$s), స్వేచ్ఛమైన, ప్రకటనలు లేని, ఓపెన్ సోర్స్ చెస్ సర్వర్. + నిజంగా + సహకరించండి + సేవా నిబంధనలు + సోర్స్ కోడ్ + Simultaneous exhibitions + హోస్ట్ + హోస్ట్ రంగు: %s + కొత్తగా సృష్టించబడిన simuls + కొత్త simul చేయండి + Simul కనుబడలేదు + ఈ simultaneous exhibition లేదు. + Simul హోమ్‌పేజీకి తిరిగి వెళ్ళు + Simuls ఒకే ఆటగాడు ఒకేసారి అనేక మంది ఆటగాళ్లను ఎదుర్కొంటాడు. + 50 మంది ప్రత్యర్థులలో, ఫిషర్ 47 గేమ్‌లు గెలిచాడు, 2 డ్రా చేసుకున్నాడు మరియు 1 ఓడిపోయాడు. diff --git a/translation/dest/site/vi-VN.xml b/translation/dest/site/vi-VN.xml index 2eb94c3df7..6b2655ce9a 100644 --- a/translation/dest/site/vi-VN.xml +++ b/translation/dest/site/vi-VN.xml @@ -46,6 +46,8 @@ Đen chịu thua Trắng đã thoát khỏi ván cờ Đen đã thoát khỏi ván cờ + Bên trắng không đi quân + Bên đen không đi quân Yêu cầu máy tính phân tích Máy tính phân tích Máy tính phân tích có sẵn @@ -83,6 +85,7 @@ Chiếu hết trong %s nước + DTZ50\"\" được làm tròn, dựa vào số nước đi quân cho tới nước bắt quân hoặc đi tốt tiếp theo Không có ván đấu nào trùng khớp Bạn có muốn thêm nhiều ván đấu hơn từ mục lục? Sách khai cuộc diff --git a/translation/dest/site/zh-CN.xml b/translation/dest/site/zh-CN.xml index 9bf07f6add..d41bac5564 100644 --- a/translation/dest/site/zh-CN.xml +++ b/translation/dest/site/zh-CN.xml @@ -92,6 +92,7 @@ %s开局浏览器 因50回合规则未赢棋 因50回合规则未输棋 + 由于先前的错误而损失或打平局(rule50) 好了! 导入PGN文件 删除 @@ -151,7 +152,7 @@ 棋手 棋友 讨论组 - 今天 + 今天 昨天 各方限时(分钟) 变体 @@ -401,9 +402,9 @@ 常见开局 残局局面 Chess960 起始局面: %s - 起始局面 + 起始局面 清空棋盘 - 载入局面 + 载入局面 私人锦标赛 举报%s 资料完成度:%s @@ -422,7 +423,7 @@ 过去的 Lichess 直播 在线棋手 活跃棋手 - 注意,此棋局是排位赛,但是不计时! + 注意,此棋局是排位赛,但是不计时! 解题成功 %s 场对局进行中 @@ -490,7 +491,7 @@ 在慢棋时 总是 从不 - %1$s参加%2$s + %1$s参加%2$s 获胜 负败 %1$s,与 %2$s 进行的 %3$s @@ -769,7 +770,7 @@ 您还不能在论坛发表意见。再下几局棋吧! 订阅 取消订阅 - 在 “%1$s” 中提到了你。 + 在 “%1$s” 中提到了你。 %1$s 在 \"%2$s\" 中提到了你。 邀请你加入\"%1$s\"。 %1$s 邀请你加入 \"%2$s\"。 diff --git a/translation/dest/storm/be-BY.xml b/translation/dest/storm/be-BY.xml index 07647d99e7..34f917c268 100644 --- a/translation/dest/storm/be-BY.xml +++ b/translation/dest/storm/be-BY.xml @@ -4,11 +4,11 @@ Вы гуляеце белымі фігурамі ва ўсіх задачах Вы гуляеце чорнымі фігурамі ва ўсіх задачах задач вырашана - Новы дзенны рэкорд! + Новы дзённы рэкорд! Новы тыднёвы рэкорд! Новы месячны рэкорд! - Новы рэкорд за ўсе часы! - Папярэні рэкорд быў %s + Новы рэкорд за ўвесь час! + Папярэдні рэкорд %s Гуляць яшчэ раз 1 спроба diff --git a/translation/dest/storm/de-CH.xml b/translation/dest/storm/de-CH.xml index b351a947f8..dc8a95dc2a 100644 --- a/translation/dest/storm/de-CH.xml +++ b/translation/dest/storm/de-CH.xml @@ -52,4 +52,10 @@ Überschpring dä Zug, zum dini Kombination byzbhaltä! Du chasch das nur eimol pro Ränä machä. Fählgschlagi Ufgoobä Langsami Ufgoobä + Die Wuche + De Monät + Insgesamt + Klick, zum neu lade + De Durchgang isch abgeloffen! + De Durchgang isch imene andere Fenster göffnet worde! diff --git a/translation/dest/storm/it-IT.xml b/translation/dest/storm/it-IT.xml index 66e236db85..2888fbf1b1 100644 --- a/translation/dest/storm/it-IT.xml +++ b/translation/dest/storm/it-IT.xml @@ -56,4 +56,6 @@ Questo mese Sempre Clicca per ricaricare + Questa serie è scaduta! + Questa serie è stata aperta in un\'altra scheda! diff --git a/translation/dest/storm/or-IN.xml b/translation/dest/storm/or-IN.xml index 27379a1835..71c684aad3 100644 --- a/translation/dest/storm/or-IN.xml +++ b/translation/dest/storm/or-IN.xml @@ -4,6 +4,7 @@ 1 ରନ୍ %s ରନ୍ + ସ୍କୋର୍ ସମୟ ଏକ ନୂତନ ଖେଳ ସୃଷ୍ଟି କରନ୍ତୁ ବାଦ୍ ଦିଅ diff --git a/translation/dest/storm/zh-CN.xml b/translation/dest/storm/zh-CN.xml index f9e12874f0..746eb67784 100644 --- a/translation/dest/storm/zh-CN.xml +++ b/translation/dest/storm/zh-CN.xml @@ -54,4 +54,6 @@ 本月 全部时间 点击重新加载 + 本次冲刺已超时! + 本次冲刺已经在另一个标签页中打开! diff --git a/translation/dest/streamer/de-CH.xml b/translation/dest/streamer/de-CH.xml index 88cf0e4fb4..2c1ac39724 100644 --- a/translation/dest/streamer/de-CH.xml +++ b/translation/dest/streamer/de-CH.xml @@ -18,7 +18,7 @@ Füäg s\'Stichwort \"lichess.org\" i dim Schtriimtitäl ii und verwänd d\'Kategorie \"Chess\", wänn du uf Lichess schtriimsch. Entfärn s\'Stichwort, wänn du grad nöd Lichess-bezogä schtriimsch. Lichess wird automatisch din Schtriim ärkänä und folgändi Zuäsatzdiänscht aktiviärä: - Lies unsere %s, um Fairplay für alle während deines Streams zu gewährleisten. + Liis eusi %s, um Fairplay für alli während dim Schtriim z\'gwährleistä. Schtriimer Fairplay FAQ Vorteil fürs Schtriimä mitämä Schlüssälwort Bechum äs flammänds Schtriimer-Symbol uf dim Lichess-Profil. diff --git a/translation/dest/streamer/lb-LU.xml b/translation/dest/streamer/lb-LU.xml index b656902365..6169aa9a20 100644 --- a/translation/dest/streamer/lb-LU.xml +++ b/translation/dest/streamer/lb-LU.xml @@ -21,8 +21,10 @@ Lies eis %s fir Fairplay fir jiddereen wärend denge Streams ze garantéieren. Streaming Fairplay FAQ Virdeeler fir Streaming mat engem Stéchwuert + Twitch Benotzernumm oder URL Iwwerschrëft Laang Beschreiwung + %s Streamer Bild Däin Bild änneren/läschen E Bild héichlueden Maximal Gréisst: %s diff --git a/translation/dest/study/be-BY.xml b/translation/dest/study/be-BY.xml index 1824412e56..f6de5fa8b2 100644 --- a/translation/dest/study/be-BY.xml +++ b/translation/dest/study/be-BY.xml @@ -46,7 +46,8 @@ Вы цяпер рэдактар Вы цяпер глядач Тэгі PGN - Падабайка + Упадабаць + Разпадабаць Новы тэг Каментаваць пазіцыю Каментаваць гэты ход diff --git a/translation/dest/study/de-CH.xml b/translation/dest/study/de-CH.xml index a0240603dc..5e05585522 100644 --- a/translation/dest/study/de-CH.xml +++ b/translation/dest/study/de-CH.xml @@ -41,6 +41,7 @@ Du bist jetzt Zuschauer PGN Tags Gefällt mir + Gfallt mer nüme Neuer Tag Kommentiere diese Stellung Kommentiere diesen Zug diff --git a/translation/dest/study/he-IL.xml b/translation/dest/study/he-IL.xml index 6ab068ebb2..c80f978003 100644 --- a/translation/dest/study/he-IL.xml +++ b/translation/dest/study/he-IL.xml @@ -134,6 +134,7 @@ נקה צ\'אט למחוק את ההיסטוריה של הצ\'אט של המחקר? אין דרך חזרה! מחק מחקר + האם למחוק את כל הלימוד? אין דרך חזרה! הקלד את שם הלימוד לאישור: %s איפה אתה מעוניין לחקור זאת? מסע טוב טעות @@ -143,6 +144,8 @@ מסע מפוקפק המסע היחיד כפאי + עמדה מאוזנת + עמדה לא ברורה יתרון קל ללבן יתרון קל לשחור לבן יותר טוב @@ -151,5 +154,13 @@ שחור מנצח חידוש פיתוח + יוזמה התקפה + משחק נגד + מצוקת זמן + עם פיצוי + עם הרעיון + פרק הבא + הפרק הקודם + פעולות לימוד diff --git a/translation/dest/study/hi-IN.xml b/translation/dest/study/hi-IN.xml index 6247aba8c1..fe86d896f8 100644 --- a/translation/dest/study/hi-IN.xml +++ b/translation/dest/study/hi-IN.xml @@ -15,6 +15,7 @@ जोड़ा गया (सबसे पुराना) हाल ही में अद्यतित सबसे लोकप्रिय + वर्णक्रमानुसार एक नया अध्याय जोड़ें %s अध्याय @@ -40,6 +41,7 @@ अब आप एक दर्शक हैं PGN टैग लाइक + नापसन्द करे नया टैग इस स्थिति पर टिप्पणी करें इस चाल पर टिप्पणी करें @@ -126,4 +128,9 @@ क्या इस पड़ाई से सम्बन्धित बातों को मिटा देना चाहिए? इससे पीछे जाने का कोई रास्ता शेष नहीं है! अध्याय को मिटा दे आप इसको खा से पड़ना चाहते है + अच्छी चाल! + गलती + अद्भुत चाल​। + भयंकर गलती + दिलचस्प चाल​ | diff --git a/translation/dest/study/hr-HR.xml b/translation/dest/study/hr-HR.xml index 9541dc2742..614e75d015 100644 --- a/translation/dest/study/hr-HR.xml +++ b/translation/dest/study/hr-HR.xml @@ -43,7 +43,7 @@ Postao si gledatelj PGN oznake Sviđa mi se - Ne sviđa mi se + Ne sviđa mi se Nova oznaka Komentiraj ovu poziciju Komentiraj ovaj potez @@ -131,4 +131,16 @@ Dali želite obrisati povijest razgovora? Nakon ovoga nema povratka! Izbriši studiju Gdje želiš to studirati? + Dobar potez + Greška + Briljantan potez + Gruba greška + Zanimljiv potez + Sumnjiv potez + Jedini potez + Nov potez + Razvoj + Inicijativa + Napad + Protunapad diff --git a/translation/dest/study/id-ID.xml b/translation/dest/study/id-ID.xml index 60a28050dc..780f2759b1 100644 --- a/translation/dest/study/id-ID.xml +++ b/translation/dest/study/id-ID.xml @@ -38,6 +38,7 @@ Sekarang Anda adalah penonton Tagar PGN Suka + Batal Suka Tagar baru Komentar di posisi ini Komentari langkah ini @@ -122,6 +123,7 @@ Bersihkan obrolan Hapus riwayat obrolan studi? Ini tidak akan dapat mengulangkan kembali! Hapus studi + Hapus seluruh studi? Tidak dapat kembal lagi! Tuliskan nama studi untuk konfirmasi: %s Dimana Anda ingin mempelajarinya? Langkah bagus Kesalahan diff --git a/translation/dest/study/kk-KZ.xml b/translation/dest/study/kk-KZ.xml index 5f3ddfafb9..31d2c0ea5b 100644 --- a/translation/dest/study/kk-KZ.xml +++ b/translation/dest/study/kk-KZ.xml @@ -15,6 +15,7 @@ Құрылған күні (ескіден) Жақында құрылған Ең танымалдары + Әліппе ретімен Жаңа бөлім құру %s бөлім @@ -40,6 +41,7 @@ Сіз енді көрерменсіз PGN тэгтері Ұнату + Ұнатпаймын Жаңа тэг Осы тақта күйі туралы пікір қалдыру Осы жүріс туралы пікір қалдыру @@ -125,5 +127,33 @@ Чатты өшіру Зерттеудің чат тарихын өшіресіз бе? Кері жол жоқ! Зерттеуді жою + Бүкіл зерттеуді жоясыз ба? Қайтар жол жоқ. Растау үшін зерттеу атауын жазыңыз: %s Бұл күйдің зерттеуін қай жерде бастайсыз? + Жақсы жүріс + Қате + Әдемі жүріс + Өрескел қателік + Қызық жүріс + Күмәнді жүріс + Жалғыз жүріс + Цугцванг + Күйлері шамалас + Күйі анық емес + Ақ сәл күштірек + Қара сәл күштірек + Ақтың жағдайы жақсы + Қараның жағдайы жақсы + Ақ жеңеді + Қара жеңеді + Жаңашылдық + Дамыту + Белсенді + Шабуыл + Қарсы шабуыл + Уақыт қаупі + Өтеумен + Бір оймен + Келесі бөлім + Алдыңғы бөлім + Зерттеу әрекеттері diff --git a/translation/dest/study/lb-LU.xml b/translation/dest/study/lb-LU.xml index 864d99f137..fb24d23a3c 100644 --- a/translation/dest/study/lb-LU.xml +++ b/translation/dest/study/lb-LU.xml @@ -41,7 +41,7 @@ Du bass elo en Zuschauer PGN Tags Gefällt mir - Gefällt mer net méi + Gefällt mer net méi Néien Tag Kommentéier des Positioun Kommentéier dësen Zuch @@ -59,11 +59,11 @@ Lescht Säit Deelen & exportéieren Klonen - PGN studéieren + Etüden PGN All Partien eroflueden Kapitel PGN Partie eroflueden - Etüd URL + Etüden URL Aktuellt Kapitel URL Zum Anbetten an een Forum oder Blog afügen Mat Startpositioun ufänken diff --git a/translation/dest/study/or-IN.xml b/translation/dest/study/or-IN.xml index 76e15360d6..0ea8093111 100644 --- a/translation/dest/study/or-IN.xml +++ b/translation/dest/study/or-IN.xml @@ -9,17 +9,17 @@ %sଟି ଅଧ୍ୟାୟ ଅଧ୍ୟୟନକୁ ଆମନ୍ତ୍ରଣ କରନ୍ତୁ - ଅଧ୍ୟୟନ ଛାଡ଼ି ଦିଅନ୍ତୁ + ଅଧ୍ୟୟନ ଛାଡ଼ି ଦିଅନ୍ତୁ PGN ଟ୍ୟାଗ୍ ପସନ୍ଦ କରନ୍ତୁ - ପସନ୍ଦ ହଟାନ୍ତୁ + ପସନ୍ଦ ହଟାନ୍ତୁ ଅଂଶୀଦାର ଓ ରପ୍ତାନି ଅଧ୍ୟୟନର PGN ଅଧ୍ୟାୟର PGN ଅଧ୍ୟୟନ URL ଆପଣଙ୍କ ୱେବସାଇଟ୍ ରେ ଗ୍ରନ୍ଥନ କରନ୍ତୁ ଗ୍ରନ୍ଥନ ବିଷୟରେ ଅଧିକ ପଢ଼ନ୍ତୁ - କେବଳ ସାର୍ବଜନୀନ ଅଧ୍ୟୟନଗୁଡ଼ିକ ଗ୍ରଥିତ ହୋଇପାରିବ! + କେବଳ ସାର୍ବଜନୀନ ଅଧ୍ୟୟନଗୁଡ଼ିକ ଗ୍ରଥିତ ହୋଇପାରିବ! ଖୋଲନ୍ତୁ ଅଧ୍ୟୟନ ମିଳିଲା ନାହିଁ ଅଧ୍ୟାୟ ସମ୍ପାଦନା diff --git a/translation/dest/swiss/de-CH.xml b/translation/dest/swiss/de-CH.xml index 676e31d477..1960411642 100644 --- a/translation/dest/swiss/de-CH.xml +++ b/translation/dest/swiss/de-CH.xml @@ -38,7 +38,7 @@ Wänn sölläd Turniär noch Schwiizer Syschtem anschtatt Arena-Turniär verwändät wärdä? Imänä Turniär mit Schwiiizer System, spiiläd alli glich viil partie und jede spielt nur einisch gege jede andär. Ds chöönt ä guäti Waal si für Cklubs und offiziäli Turniär. Wiä wärdäd Pünkt berächnät? - Ein Sieg ist einen Punkt wert, ein Remis einen halben Punkt, und eine Niederlage null Punkte. Wenn ein Spieler während einer Runde keinem anderen Spieler zugeteilt werden kann, erhält er einen Punkt. + En Sieg isch ein Punkt wärt, es Remi zwei, und wenn verlürsch bechumsch keine. Wenn du kein Gänger hesch, bechumsch eifoch en Punkt. Wie wärdäd Tie-Breaks berächnät? Sonneborn-Berger-Wärtig Mit dem %s. @@ -46,7 +46,7 @@ Addiere die Punkte aller Gegner, die der Spieler besiegt, und die Hälfte der Pu Wiä wärdäd Paarigä entschidä? Niderländischs Syschtem FIDE-Handbuäch - Mit dem %1$s, implementiert von %2$s, in Übereinstimmung mit den %3$s. + Mit dem %1$s, implementiert von %2$s, in Übereinstimmung mit dem %3$s. Was passiert, wenn das Turnier mehr Runden als Spieler hat? Wenn alle möglichen Paarungen gespielt wurden, wird das Turnier beendet und ein Sieger erklärt. Wieso isch es uf Teams beschränkt? @@ -75,15 +75,17 @@ Am nähesten an ein online Rundenturnier kommt, ein Turnier nach Schweizer Syste Schtartät grad Vergliich Längi vom Turniär - Vordefinierte Dauer in Minuten + Vordefinierti Duuer ih Minutä Vordefinierte maximale Rundenanzahl, aber unbekannte Dauer Aazahl Partiiä So viele wie in der zugewiesenen Dauer gespielt werden können Im Voraus entschieden, für alle Spieler gleich Paarigssüschtem - Jeder verfügbare Gegner mit ähnlichem Rang - Beste Paarung, basierend auf Punkten und Feinwertungen + Jede verfügbari Gägner mit ähnlichem Rang + Besti Paarig, basierend uf Pünkt und Feinwertungen Warteziit für d Paarig + Schnäll: wartet nöd uf all Spieler + Langsam: wartet uuf all Spieler Idäntischi Paarig Möglich, abär nöd hindäränand Verbottä diff --git a/translation/dest/swiss/fr-FR.xml b/translation/dest/swiss/fr-FR.xml index a815c3a06d..141d924bb6 100644 --- a/translation/dest/swiss/fr-FR.xml +++ b/translation/dest/swiss/fr-FR.xml @@ -19,7 +19,7 @@ %s secondes entre les rondes - %s minute entre les rondes + %s minutes entre les rondes %s minutes entre les rondes Commence dans @@ -87,8 +87,8 @@ Pourquoi? Parce qu\'il n\'existe pas de façon équitable de résoudre le probl N\'importe quel adversaire disponible avec un classement similaire Meilleur appariement selon le nombre de points et le départage Temps d\'attente pour l\'appariement - Rapide : n\'attend pas tous les joueurs - Lent : attend tous les joueurs + Rapide : n\'attend pas tous les joueurs + Lent : attend tous les joueurs Appariement identique Possible, mais pas de façon consécutive Interdit diff --git a/translation/dest/swiss/he-IL.xml b/translation/dest/swiss/he-IL.xml index b6ae168d59..0c19e83700 100644 --- a/translation/dest/swiss/he-IL.xml +++ b/translation/dest/swiss/he-IL.xml @@ -11,6 +11,8 @@ בטורניר שוויצרי, כל השחקנים משחקים אותו מספר של משחקים, ויכולים לשחק אחד נגד השני פעם אחת בלבד. זאת יכולה להיות אפשרות טובה למועדונים וטורנירים רשמיים. האם טורנירים שוויצריים יחליפו את טורנירי הזירות? לא. הם מאפיינים משלימים. + משך הטורניר + מספר משחקים כל יריב זמין עם דירוג דומה מהיר: אין צורך לחכות לשאר השחקנים איטי: צריך לחכות לסיום המשחקים של כל השחקנים diff --git a/translation/dest/swiss/id-ID.xml b/translation/dest/swiss/id-ID.xml index f163b3563f..ba011035d5 100644 --- a/translation/dest/swiss/id-ID.xml +++ b/translation/dest/swiss/id-ID.xml @@ -22,10 +22,15 @@ Permainan yang sedang berlangsung + Tanggal mulai turnamen Jumlah babak Sebuah babak yang ganjil mengizinkan keseimbangan warna yang optimal. + Jarak antar ronde Turnamen Swiss baru penilaian Sonneborn–Berger + Dapatkah pemain telat bergabung? + Ya, sampai lebih dari setengah putaran dimulai; misalnya dalam 11 ronde swiss, pemain dapat bergabung sebelum ronde 6 dimulai dan dalam 12 ronde, sebelum ronde 7 dimulai. +Peserta yang terlambat mendapatkan satu \"bye\", bahkan jika mereka melewatkan beberapa ronde. Sedang berlangsung Segera dimulai Perbandingan diff --git a/translation/dest/team/id-ID.xml b/translation/dest/team/id-ID.xml index 8b736bdcd8..38f6915a2c 100644 --- a/translation/dest/team/id-ID.xml +++ b/translation/dest/team/id-ID.xml @@ -6,6 +6,7 @@ %s anggota Semua tim + Tim yang dipimpin Tim baru Tim saya Tidak ada tim yang ditemukan @@ -24,6 +25,7 @@ Permintaan bergabung Anda akan ditinjau oleh ketua tim. Permintaan bergabung Anda telah ditinjau oleh ketua tim. + Permintaan bergabung Anda telah ditolak oleh ketua tim. Berlangganan pesan tim Pertarungan Tim Pertarungan antara bebrapa tim, setiap pemain mencetak poin untuk timnya @@ -47,4 +49,5 @@ Pemain yang tidak suka menerima pesan anda dapat meninggalkan tim. Tim ini sudah ada. Turnamen selanjutnya Turnamen yang telah selesai + Permintaan yang Ditolak diff --git a/translation/dest/team/or-IN.xml b/translation/dest/team/or-IN.xml index ffed1d7fc6..3a1526b68e 100644 --- a/translation/dest/team/or-IN.xml +++ b/translation/dest/team/or-IN.xml @@ -1,7 +1,7 @@ Team - ଦଳଗୁଡ଼ିକ + ଦଳଗୁଡ଼ିକ ସମସ୍ତ ଦଳ ନୂତନ ଦଳ ମୋର ଦଳଗୁଡିକ @@ -17,7 +17,7 @@ ଆପଣ କାହାକୁ ଦଳରୁ ବାହାର କରିବାକୁ ଚାହୁଁଛନ୍ତି? ଆପଣଙ୍କର ଯୋଗଦାନ ଅନୁରୋଧ, ଦଳର ନେତାଙ୍କ ଦ୍ୱାରା ସମୀକ୍ଷା କରାଯିବ ଆପଣଙ୍କର ଯୋଗଦାନ ଅନୁରୋଧ, ଦଳର ନେତାଙ୍କ ଦ୍ୱାରା ସମୀକ୍ଷା କରାଯାଉଛି - ଦଳ ବାର୍ତ୍ତାଗୁଡ଼ିକୁ ସଦସ୍ୟ ହୁଅନ୍ତୁ + ଦଳ ବାର୍ତ୍ତାର ସଦସ୍ୟ ହୁଅନ୍ତୁ ଦଳ ଯୁଦ୍ଧ ଏକାଧିକ ଦଳର ଯୁଦ୍ଧରେ, ପ୍ରତ୍ୟେକ ଖେଳାଳି ନିଜ ଦଳ ପାଇଁ ପଏଣ୍ଟ ସ୍କୋର କରନ୍ତି ଦଳ ଟୁର୍ନାମେଣ୍ଟ diff --git a/translation/dest/ublog/ca-ES.xml b/translation/dest/ublog/ca-ES.xml index 8425623a99..326eba9676 100644 --- a/translation/dest/ublog/ca-ES.xml +++ b/translation/dest/ublog/ca-ES.xml @@ -11,10 +11,26 @@ Publicat Publica al teu blog Si es marca l\'entrada es publicarà al teu blog. Sinó, serà privada i només es mostrarà en les publicacions en esborrany + + Ha publicat una entrada nova + Ha publicat %s entrades noves + + + Una visualització + %s visualitzacions + + %1$s ha publicat %2$s Aquesta entrada està publicada Això és un esborrany + Més publicacions de %s + Encara no hi ha publicacions en aquest blog. Cap esborrany a mostrar. Les últimes publicacions en el blog + + Veure una publicació + Veure les %s publicacions + Puja una imatge de per la teva entrada + Text alternatiu de la imatge Crèdits de la imatge diff --git a/translation/dest/ublog/cs-CZ.xml b/translation/dest/ublog/cs-CZ.xml index b8088f6cee..6f354d3216 100644 --- a/translation/dest/ublog/cs-CZ.xml +++ b/translation/dest/ublog/cs-CZ.xml @@ -1,5 +1,6 @@ + Blog hráče %s Nový příspěvek Upravit svůj příspěvek na blogu Uložit koncept diff --git a/translation/dest/ublog/de-CH.xml b/translation/dest/ublog/de-CH.xml index 56befb4257..fe56ae8e06 100644 --- a/translation/dest/ublog/de-CH.xml +++ b/translation/dest/ublog/de-CH.xml @@ -10,17 +10,17 @@ Entwürf Veröffentlicht Veröffentlich i dim Blog - Wenn aktiviert, wird der Beitrag in deinem Blog aufgelistet. Wenn nicht, wird er privat sein, in deinen Entwürfen + Wänn aktiviert wird de Biitrag ih diim Blog uufgelistet. Wenn nöd wird er ih diine Entwürf privat sii - Hat einen Blog-Beitrag veröffentlicht - Hat %s Blog-Beiträge veröffentlicht + Hät en Blog-Biitrag veröffentlicht + Hät %s Blog-Biiträg veröffentlicht Eimal aagluägt %s-mal aagluägt - %1$s hat %2$s veröffentlicht - Dieser Beitrag wurde veröffentlicht + %1$s hät %2$s veröffentlicht + De Biitrag isch veröffentlicht worde Das isch än Entwurf Meh Blog-Posts vo %s No kei Posts i dem Blog. diff --git a/translation/dest/ublog/en-US.xml b/translation/dest/ublog/en-US.xml index 45e7301b86..74034e2810 100644 --- a/translation/dest/ublog/en-US.xml +++ b/translation/dest/ublog/en-US.xml @@ -31,6 +31,6 @@ View all %s posts Upload an image for your post - Image alternative text - Image credit + Image alternative text + Image credit diff --git a/translation/dest/ublog/ko-KR.xml b/translation/dest/ublog/ko-KR.xml index 3ea04e700d..bcb373c436 100644 --- a/translation/dest/ublog/ko-KR.xml +++ b/translation/dest/ublog/ko-KR.xml @@ -1,2 +1,12 @@ - + + %s의 블로그 + 새 글 쓰기 + 제목 + 본문 + 임시 보관 글 + 게시된 글 + 더 많은 %s의 글 + 이 블로그에는 아직 글이 없습니다. + 임시저장된 글이 없습니다. + diff --git a/translation/dest/ublog/or-IN.xml b/translation/dest/ublog/or-IN.xml index 0878279911..dedf91670c 100644 --- a/translation/dest/ublog/or-IN.xml +++ b/translation/dest/ublog/or-IN.xml @@ -2,13 +2,13 @@ ପ୍ରକାଶିତ ହୋଇଛି ଆପଣଙ୍କ ବ୍ଲଗ୍ ରେ ପ୍ରକାଶନ କରନ୍ତୁ - ଯଦି ଯାଞ୍ଚ କରାଯାଏ, ତେବେ ପୋଷ୍ଟଟି ଆପଣଙ୍କ ବ୍ଲଗରେ ତାଲିକାଭୁକ୍ତ ହେବ। ଯଦି ନୁହେଁ, ତେବେ ଏହା ଆପଣଙ୍କ ଡ୍ରାଫ୍ଟ ପୋଷ୍ଟଗୁଡ଼ିକରେ ବ୍ୟକ୍ତିଗତ ରହିବ + ଯଦି ଯାଞ୍ଚ କରାଯାଏ, ତେବେ ପୋଷ୍ଟଟି ଆପଣଙ୍କ ବ୍ଲଗରେ ତାଲିକାଭୁକ୍ତ ହେବ। ଯଦି ନୁହେଁ, ତେବେ ଏହା ଆପଣଙ୍କ ଡ୍ରାଫ୍ଟ ପୋଷ୍ଟଗୁଡ଼ିକରେ ବ୍ୟକ୍ତିଗତ ରହିବ ଏକ ବ୍ଲଗ୍ ପୋଷ୍ଟ ପ୍ରକାଶିତ ହୋଇଛି %sଟି ବ୍ଲଗ୍ ପୋଷ୍ଟ ପ୍ରକାଶିତ ହୋଇଛି %1$s %2$s ପ୍ରକାଶ କରିଛନ୍ତି ଏହି ପୋଷ୍ଟ ପ୍ରକାଶିତ ହୋଇଛି - ପ୍ରତିଛବି ବିକଳ୍ପ ପାଠ୍ୟ - ପ୍ରତିଛବି କ୍ରେଡିଟ୍ + ପ୍ରତିଛବି ବିକଳ୍ପ ପାଠ୍ୟ + ପ୍ରତିଛବି କ୍ରେଡିଟ୍ diff --git a/translation/source/challenge.xml b/translation/source/challenge.xml index dedbdae844..097a4fce7f 100644 --- a/translation/source/challenge.xml +++ b/translation/source/challenge.xml @@ -4,7 +4,7 @@ Challenge to a game Challenge declined Challenge accepted! - Challenge canceled. + Challenge cancelled. Please register to send challenges. You cannot challenge %s. %s does not accept challenges. diff --git a/ui/@types/lichess/index.d.ts b/ui/@types/lichess/index.d.ts index 1520451db0..382eeca0e2 100644 --- a/ui/@types/lichess/index.d.ts +++ b/ui/@types/lichess/index.d.ts @@ -1,5 +1,5 @@ interface Lichess { - load: Promise; // window.onload promise + load: Promise; // DOMContentLoaded promise info: any; requestIdleCallback(f: () => void, timeout?: number): void; sri: string; @@ -102,7 +102,7 @@ interface UserCompleteOpts { } interface SoundI { - loadOggOrMp3(name: string, path: string): void; + loadOggOrMp3(name: string, path: string, noSoundSet?: boolean): void; loadStandard(name: string, soundSet?: string): void; play(name: string, volume?: number): void; playOnce(name: string): void; diff --git a/ui/analyse/css/_action-menu.scss b/ui/analyse/css/_action-menu.scss index 6ceb904b4f..c1945231b3 100644 --- a/ui/analyse/css/_action-menu.scss +++ b/ui/analyse/css/_action-menu.scss @@ -82,11 +82,6 @@ text-align: right; margin-top: 6px; - .button { - display: inline-block; - padding: 0 8px; - } - .button::before { font-size: 1.2em; } diff --git a/ui/analyse/css/_player-clock.scss b/ui/analyse/css/_player-clock.scss index bcef13b290..b095074075 100644 --- a/ui/analyse/css/_player-clock.scss +++ b/ui/analyse/css/_player-clock.scss @@ -14,6 +14,10 @@ $clock-height: 20px; &.bottom { top: var(--cg-height, 100%); z-index: 1; // over the board coords + + .is3d & { + top: calc(var(--cg-height, 100%) + 15px); + } } /* Where to put them in col1 layout? It moves the entire board and controls down for little benefit */ diff --git a/ui/analyse/src/ctrl.ts b/ui/analyse/src/ctrl.ts index 5026dfb458..89e0d76c72 100644 --- a/ui/analyse/src/ctrl.ts +++ b/ui/analyse/src/ctrl.ts @@ -389,7 +389,7 @@ export default class AnalyseCtrl { userJump = (path: Tree.Path): void => { this.autoplay.stop(); - this.withCg(cg => cg.selectSquare(null)); + if (!this.gamebookPlay()) this.withCg(cg => cg.selectSquare(null)); if (this.practice) { const prev = this.path; this.practice.preUserJump(prev, path); @@ -794,7 +794,8 @@ export default class AnalyseCtrl { this.tree.merge(data.tree); if (!this.showComputer()) this.tree.removeComputerVariations(); this.data.analysis = data.analysis; - if (data.analysis) data.analysis.partial = !!treeOps.findInMainline(data.tree, n => !n.eval && !!n.children.length); + if (data.analysis) + data.analysis.partial = !!treeOps.findInMainline(data.tree, n => !n.eval && !!n.children.length && n.ply <= 300); if (data.division) this.data.game.division = data.division; if (this.retro) this.retro.onMergeAnalysisData(); if (this.study) this.study.serverEval.onMergeAnalysisData(); diff --git a/ui/analyse/src/evalCache.ts b/ui/analyse/src/evalCache.ts index e38e9a3ef6..29e5ceb2e3 100644 --- a/ui/analyse/src/evalCache.ts +++ b/ui/analyse/src/evalCache.ts @@ -73,12 +73,20 @@ function toCeval(e: Tree.ServerEval): Tree.ClientEval { export function make(opts: EvalCacheOpts): EvalCache { let fetchedByFen: Dictionary = {}; const upgradable = prop(false); - lichess.pubsub.on('socket.in.crowd', d => upgradable(d.nb > 2)); + lichess.pubsub.on('socket.in.crowd', d => upgradable(d.nb > 2 && d.nb < 99999)); return { - onCeval: throttle(500, function () { + onCeval: throttle(500, () => { const node = opts.getNode(), ev = node.ceval; - if (ev && !ev.cloud && node.fen in fetchedByFen && qualityCheck(ev) && opts.canPut()) { + const fetched = fetchedByFen[node.fen]; + if ( + ev && + !ev.cloud && + node.fen in fetchedByFen && + (!fetched || fetched.depth < ev.depth) && + qualityCheck(ev) && + opts.canPut() + ) { opts.send('evalPut', toPutData(opts.variant, ev)); } }), diff --git a/ui/analyse/src/explorer/explorerConfig.ts b/ui/analyse/src/explorer/explorerConfig.ts index c3f947e2fb..833443eb19 100644 --- a/ui/analyse/src/explorer/explorerConfig.ts +++ b/ui/analyse/src/explorer/explorerConfig.ts @@ -18,6 +18,13 @@ const allRatings = [1600, 1800, 2000, 2200, 2500]; const minYear = 1952; type Month = string; +type ByDbSetting = { + since: StoredProp; + until: StoredProp; +}; +type ByDbSettings = { + [key in ExplorerDb]: ByDbSetting; +}; export interface ExplorerConfigData { open: Prop; @@ -25,14 +32,14 @@ export interface ExplorerConfigData { rating: StoredJsonProp; speed: StoredJsonProp; mode: StoredJsonProp; - since: StoredProp; - until: StoredProp; + byDbData: ByDbSettings; playerName: { open: Prop; value: StoredProp; previous: StoredJsonProp; }; color: Prop; + byDb(): ByDbSetting; } export class ExplorerConfigCtrl { @@ -43,20 +50,29 @@ export class ExplorerConfigCtrl { constructor(readonly root: AnalyseCtrl, readonly variant: VariantKey, readonly onClose: () => void) { this.myName = document.body.dataset['user']; if (variant === 'standard') this.allDbs.unshift('masters'); + const byDbData = {} as ByDbSettings; + for (const db of this.allDbs) { + byDbData[db] = { + since: storedProp('explorer.since-2.' + db, ''), + until: storedProp('explorer.until-2.' + db, ''), + }; + } this.data = { open: prop(false), db: storedProp('explorer.db.' + variant, this.allDbs[0]), rating: storedJsonProp('explorer.rating', () => allRatings), speed: storedJsonProp('explorer.speed', () => allSpeeds), mode: storedJsonProp('explorer.mode', () => allModes), - since: storedProp('explorer.since-2', ''), - until: storedProp('explorer.until-2', ''), + byDbData, playerName: { open: prop(false), value: storedProp('explorer.player.name', document.body.dataset['user'] || ''), previous: storedJsonProp('explorer.player.name.previous', () => []), }, color: prop('white'), + byDb() { + return this.byDbData[this.db() as ExplorerDb] || this.byDbData.lichess; + }, }; } @@ -91,8 +107,8 @@ export class ExplorerConfigCtrl { }; fullHouse = () => - this.data.since() <= `${minYear}-01` && - (!this.data.until() || new Date().toISOString().slice(0, 7) <= this.data.until()) && + this.data.byDb().since() <= `${minYear}-01` && + (!this.data.byDb().until() || new Date().toISOString().slice(0, 7) <= this.data.byDb().until()) && (this.data.db() === 'masters' || this.data.speed().length == allSpeeds.length) && (this.data.db() !== 'lichess' || this.data.rating().length == allRatings.length) && (this.data.db() !== 'player' || this.data.mode().length == allModes.length); @@ -155,8 +171,8 @@ const playerDb = (ctrl: ExplorerConfigCtrl) => { const masterDb = (ctrl: ExplorerConfigCtrl) => h('div', [ h('section.date', [ - h('label', ['Since', yearInput(ctrl.data.since, () => '', ctrl.root.redraw)]), - h('label', ['Until', yearInput(ctrl.data.until, ctrl.data.since, ctrl.root.redraw)]), + h('label', ['Since', yearInput(ctrl.data.byDb().since, () => '', ctrl.root.redraw)]), + h('label', ['Until', yearInput(ctrl.data.byDb().until, ctrl.data.byDb().since, ctrl.root.redraw)]), ]), ]); @@ -258,8 +274,8 @@ const yearInput = (prop: StoredProp, after: () => Month, redraw: Redraw) const monthSection = (ctrl: ExplorerConfigCtrl) => h('section.date', [ - h('label', ['Since', monthInput(ctrl.data.since, () => '', ctrl.root.redraw)]), - h('label', ['Until', monthInput(ctrl.data.until, ctrl.data.since, ctrl.root.redraw)]), + h('label', ['Since', monthInput(ctrl.data.byDb().since, () => '', ctrl.root.redraw)]), + h('label', ['Until', monthInput(ctrl.data.byDb().until, ctrl.data.byDb().since, ctrl.root.redraw)]), ]); const playerModal = (ctrl: ExplorerConfigCtrl) => { diff --git a/ui/analyse/src/explorer/explorerView.ts b/ui/analyse/src/explorer/explorerView.ts index b95ee6ab04..47ba17d236 100644 --- a/ui/analyse/src/explorer/explorerView.ts +++ b/ui/analyse/src/explorer/explorerView.ts @@ -358,7 +358,7 @@ const explorerTitle = (explorer: ExplorerCtrl) => { nodes ); const playerName = explorer.config.data.playerName.value(); - const masterDbExplanation = explorer.root.trans('masterDbExplanation', 2200, '1952', '2020'), + const masterDbExplanation = explorer.root.trans('masterDbExplanation', 2200, '1952', '2021'), lichessDbExplanation = 'Rated games sampled from all Lichess players'; return h('div.explorer-title', [ db == 'masters' diff --git a/ui/analyse/src/explorer/explorerXhr.ts b/ui/analyse/src/explorer/explorerXhr.ts index fb6c821c66..8054ce80e1 100644 --- a/ui/analyse/src/explorer/explorerXhr.ts +++ b/ui/analyse/src/explorer/explorerXhr.ts @@ -20,17 +20,18 @@ export async function opening( processData: (data: ExplorerData) => void ): Promise { const conf = opts.config; + const confByDb = conf.byDb(); const url = new URL(`/${opts.db}`, opts.endpoint); const params = url.searchParams; params.set('variant', opts.variant || 'standard'); params.set('fen', opts.rootFen); params.set('play', opts.play.join(',')); if (opts.db === 'masters') { - if (conf.since()) params.set('since', conf.since().split('-')[0]); - if (conf.until()) params.set('until', conf.until().split('-')[0]); + if (confByDb.since()) params.set('since', confByDb.since().split('-')[0]); + if (confByDb.until()) params.set('until', confByDb.until().split('-')[0]); } else { - if (conf.since()) params.set('since', conf.since()); - if (conf.until()) params.set('until', conf.until()); + if (confByDb.since()) params.set('since', confByDb.since()); + if (confByDb.until()) params.set('until', confByDb.until()); params.set('speeds', conf.speed().join(',')); } if (opts.db === 'lichess') { diff --git a/ui/analyse/src/study/commentForm.ts b/ui/analyse/src/study/commentForm.ts index 6bd6eb8412..40323cda9a 100644 --- a/ui/analyse/src/study/commentForm.ts +++ b/ui/analyse/src/study/commentForm.ts @@ -107,7 +107,7 @@ export function view(root: AnalyseCtrl): VNode { return h( 'div.study__comments', { - // hook: onInsert(() => root.enableWiki(true)), + hook: onInsert(() => root.enableWiki(true)), }, [ currentComments(root, !study.members.canContribute()), @@ -126,7 +126,7 @@ export function view(root: AnalyseCtrl): VNode { }, }), ]), - h('div.analyse__wiki.study__wiki.empty'), + h('div.analyse__wiki.study__wiki'), ] ); } diff --git a/ui/analyse/src/study/practice/studyPracticeCtrl.ts b/ui/analyse/src/study/practice/studyPracticeCtrl.ts index 5e1b20c0c1..669170312e 100644 --- a/ui/analyse/src/study/practice/studyPracticeCtrl.ts +++ b/ui/analyse/src/study/practice/studyPracticeCtrl.ts @@ -15,8 +15,8 @@ export default function (root: AnalyseCtrl, studyData: StudyData, data: StudyPra analysisUrl = prop(''), autoNext = storedProp('practice-auto-next', true); - lichess.sound.loadOggOrMp3('practiceSuccess', `${lichess.sound.baseUrl}/other/energy3`); - lichess.sound.loadOggOrMp3('practiceFailure', `${lichess.sound.baseUrl}/other/failure2`); + lichess.sound.loadOggOrMp3('practiceSuccess', `${lichess.sound.baseUrl}/other/energy3`, true); + lichess.sound.loadOggOrMp3('practiceFailure', `${lichess.sound.baseUrl}/other/failure2`, true); function onLoad() { root.showAutoShapes = readOnlyProp(true); diff --git a/ui/analyse/src/study/studyMembers.ts b/ui/analyse/src/study/studyMembers.ts index b53d8a9fb3..6f23bb6751 100644 --- a/ui/analyse/src/study/studyMembers.ts +++ b/ui/analyse/src/study/studyMembers.ts @@ -1,5 +1,6 @@ import { prop, Prop } from 'common'; -import { bind, onInsert, dataIcon } from 'common/snabbdom'; +import { textRaw as xhrTextRaw } from 'common/xhr'; +import { bind, onInsert, dataIcon, bindNonPassive } from 'common/snabbdom'; import { h, VNode } from 'snabbdom'; import { AnalyseSocketSend } from '../socket'; import { iconTag, scrollTo, titleNameToId } from '../util'; @@ -310,20 +311,12 @@ export function view(ctrl: StudyCtrl): VNode { 'form.admin', { key: ':admin', - attrs: { - method: 'post', - action: `/study/${ctrl.data.id}/admin`, - }, + hook: bindNonPassive('submit', () => { + xhrTextRaw(`/study/${ctrl.data.id}/admin`, { method: 'post' }).then(() => location.reload()); + return false; + }), }, - [ - h( - 'button.button.button-red.button-thin', - { - attrs: { type: 'submit' }, - }, - 'Enter as admin' - ), - ] + [h('button.button.button-red.button-thin', 'Enter as admin')] ) : null, ] diff --git a/ui/analyse/src/wiki.ts b/ui/analyse/src/wiki.ts index 822a2d279a..e39c660cb5 100644 --- a/ui/analyse/src/wiki.ts +++ b/ui/analyse/src/wiki.ts @@ -31,8 +31,8 @@ export default function wikiTheory(): WikiTheory { return debounce( async (nodes: Tree.Node[]) => { const pathParts = nodes.slice(1).map(n => `${plyPrefix(n)}${n.san}`); - const path = pathParts.join('/'); - if (!path) show(''); + const path = pathParts.join('/').replace(/[+!#?]/g, '') ?? ''; + if (pathParts.length > 30 || !path || path.length > 255 - 21) show(''); else if (cache.has(path)) show(cache.get(path)!); else if ( Array.from({ length: pathParts.length }, (_, i) => -i - 1) @@ -50,8 +50,11 @@ export default function wikiTheory(): WikiTheory { }; if (res.ok) { const json = await res.json(); - if (json.query.pages[0].missing) saveAndShow(''); - else saveAndShow(transform(json.query.pages[0].extract, title)); + const page = json.query.pages[0]; + if (page.missing) saveAndShow(''); + else if (page.invalid) show('invalid request: ' + page.invalidreason); + else if (!page.extract) show('error: unexpected API response:
' + JSON.stringify(page) + '
'); + else saveAndShow(transform(page.extract, title)); } else saveAndShow(''); } catch (err) { show('error: ' + err); diff --git a/ui/ceval/src/ctrl.ts b/ui/ceval/src/ctrl.ts index b33d94ecdc..925b505a87 100644 --- a/ui/ceval/src/ctrl.ts +++ b/ui/ceval/src/ctrl.ts @@ -117,7 +117,11 @@ export default function (opts: CevalOpts): CevalCtrl { Math.min(Math.ceil((navigator.hardwareConcurrency || 1) / 4), maxThreads) ); - const maxHashSize = Math.min(((navigator.deviceMemory || 0.25) * 1024) / 8, growableSharedMem ? 1024 : 16); + const estimatedMinMemory = technology == 'hce' || technology == 'nnue' ? 2.0 : 0.5; + const maxHashSize = Math.min( + ((navigator.deviceMemory || estimatedMinMemory) * 1024) / 8, + growableSharedMem ? 1024 : 16 + ); const hashSize = storedProp(storageKey('ceval.hash-size'), 16); const multiPv = storedProp(storageKey('ceval.multipv'), opts.multiPvDefault || 1); diff --git a/ui/ceval/src/view.ts b/ui/ceval/src/view.ts index 9abcde3f77..f2b54d6d72 100644 --- a/ui/ceval/src/view.ts +++ b/ui/ceval/src/view.ts @@ -232,7 +232,7 @@ export function renderCeval(ctrl: ParentCtrl): VNode | undefined { : h( 'div.switch', { - attrs: { title: trans.noarg('toggleLocalEvaluation') + ' (l)' }, + attrs: { title: trans.noarg('toggleLocalEvaluation') + ' (L)' }, }, [ h('input#analyse-toggle-ceval.cmn-toggle.cmn-toggle--subtle', { diff --git a/ui/common/css/component/_loader.scss b/ui/common/css/component/_loader.scss index 76139639ee..d1dd3b51e0 100644 --- a/ui/common/css/component/_loader.scss +++ b/ui/common/css/component/_loader.scss @@ -1,34 +1,49 @@ /* circular loader */ +$len1: 33.96; +$len2: 42.6; +$len3: 104.31; +$total_len: $len1 + $len2 + $len3; + @keyframes mask1 { 0% { - stroke-dashoffset: 1; + stroke-dashoffset: $total_len; } - 17.86% { - stroke-dashoffset: 0; + 100% { + stroke-dashoffset: -$total_len; } } @keyframes mask2 { 0% { - stroke-dashoffset: 1; + stroke-dashoffset: $total_len + $len1; } - 17.86% { - stroke-dashoffset: 1; - } - 31.43% { - stroke-dashoffset: 0; + 100% { + stroke-dashoffset: -$total_len + $len1; } } @keyframes mask3 { 0% { - stroke-dashoffset: 1; + stroke-dashoffset: $total_len + $len1 + $len2; } - 31.43% { - stroke-dashoffset: 1; + 100% { + stroke-dashoffset: -$total_len + $len1 + $len2; } - 85% { - stroke-dashoffset: 0; +} + +@keyframes spinner-color { + 0%, + 100% { + stroke: $c-primary; + } + 25% { + stroke: $c-error; + } + 50% { + stroke: $c-secondary; + } + 75% { + stroke: $c-brag; } } @@ -53,18 +68,25 @@ height: 70px; margin: auto; - path:nth-child(1) { - animation: mask1 2s cubic-bezier(0.417, 0.086, 0.741, 0.452) infinite; + path { + animation: mask1 2.75s cubic-bezier(0.49, 0.67, 0.45, 0.29) infinite; } path:nth-child(2) { - animation: mask2 2s cubic-bezier(0.333, 0.317, 0.621, 0.661) infinite; + animation-name: mask2; } path:nth-child(3) { - animation: mask3 2s cubic-bezier(0, 0, 0.431, 1) infinite; + animation-name: mask3; + } + + @media (prefers-reduced-motion: reduce) { + path { + animation: none !important; + } } g { - animation: spinner-color 8s steps(1) infinite; + animation: spinner-color 11s steps(1) infinite; + stroke-dasharray: $total_len $total_len; } .white & path { diff --git a/ui/common/css/vendor/chessground/_coords.scss b/ui/common/css/vendor/chessground/_coords.scss index 0c6916eaa6..8d3a5a676f 100644 --- a/ui/common/css/vendor/chessground/_coords.scss +++ b/ui/common/css/vendor/chessground/_coords.scss @@ -89,8 +89,11 @@ coords { text-align: center; } - coord { - color: $c-font-page !important; + &.ranks, + .is2d &.files { + coord { + color: $c-font-page !important; + } } } } diff --git a/ui/common/src/spinner.ts b/ui/common/src/spinner.ts index 90c7b71eef..413b7802b6 100644 --- a/ui/common/src/spinner.ts +++ b/ui/common/src/spinner.ts @@ -29,18 +29,9 @@ export default function (): VNode { attrs: { mask: 'url(#mask)', fill: 'none', - stroke: '#888', - 'stroke-dasharray': 1, }, }, - pathAttrs.map(attrs => { - return h('path', { - attrs: { - pathLength: 1, // cannot be inherited from parent group - ...attrs, - }, - }); - }) + pathAttrs.map(attrs => h('path', { attrs })) ), ]), ] diff --git a/ui/dasher/src/piece.ts b/ui/dasher/src/piece.ts index dc8c3452fe..a5cd0ded92 100644 --- a/ui/dasher/src/piece.ts +++ b/ui/dasher/src/piece.ts @@ -93,6 +93,6 @@ function applyPiece(t: Piece, list: Piece[], is3d: boolean) { $('body').removeClass(list.join(' ')).addClass(t); } else { const sprite = document.getElementById('piece-sprite') as HTMLLinkElement; - sprite.href = sprite.href.replace(/\w+\.css/, t + '.css'); + sprite.href = sprite.href.replace(/\w+(\.external|)\.css/, t + '$1.css'); } } diff --git a/ui/learn/src/sound.ts b/ui/learn/src/sound.ts index 5c5f63ad90..d15dd1dbe2 100644 --- a/ui/learn/src/sound.ts +++ b/ui/learn/src/sound.ts @@ -1,5 +1,5 @@ const make = (file: string, volume?: number) => { - lichess.sound.loadOggOrMp3(file, `${lichess.sound.baseUrl}/${file}`); + lichess.sound.loadOggOrMp3(file, `${lichess.sound.baseUrl}/${file}`, true); return () => lichess.sound.play(file, volume); }; diff --git a/ui/lobby/src/view/correspondence.ts b/ui/lobby/src/view/correspondence.ts index 8f8ba3dadf..5c93845cb8 100644 --- a/ui/lobby/src/view/correspondence.ts +++ b/ui/lobby/src/view/correspondence.ts @@ -28,7 +28,7 @@ function renderSeek(ctrl: LobbyController, seek: Seek): VNode { seek.username ) : 'Anonymous', - seek.rating + (seek.provisional ? '?' : ''), + seek.rating && ctrl.opts.showRatings ? seek.rating + (seek.provisional ? '?' : '') : '', seek.days ? ctrl.trans.plural('nbDays', seek.days) : '∞', h('span', [ h('span.varicon', { diff --git a/ui/lobby/src/view/pools.ts b/ui/lobby/src/view/pools.ts index a2235e41d9..99d55f63fc 100644 --- a/ui/lobby/src/view/pools.ts +++ b/ui/lobby/src/view/pools.ts @@ -38,7 +38,7 @@ export function render(ctrl: LobbyController) { }, [ h('div.clock', pool.lim + '+' + pool.inc), - active && member!.range ? renderRange(member!.range!) : h('div.perf', pool.perf), + active && member!.range && ctrl.opts.showRatings ? renderRange(member!.range!) : h('div.perf', pool.perf), active ? spinner() : null, ] ); diff --git a/ui/puz/src/util.ts b/ui/puz/src/util.ts index 5cb0770194..8db9dac4eb 100644 --- a/ui/puz/src/util.ts +++ b/ui/puz/src/util.ts @@ -10,7 +10,7 @@ export const uciToLastMove = (uci: string | undefined): [Key, Key] | undefined = export const puzzlePov = (puzzle: Puzzle) => opposite(parseFen(puzzle.fen).unwrap().turn); export const loadSound = (file: string, volume?: number, delay?: number) => { - setTimeout(() => lichess.sound.loadOggOrMp3(file, `${lichess.sound.baseUrl}/${file}`), delay || 1000); + setTimeout(() => lichess.sound.loadOggOrMp3(file, `${lichess.sound.baseUrl}/${file}`, true), delay || 1000); return () => lichess.sound.play(file, volume); }; diff --git a/ui/puzzle/src/ctrl.ts b/ui/puzzle/src/ctrl.ts index abb9b53948..cae4fc41c7 100644 --- a/ui/puzzle/src/ctrl.ts +++ b/ui/puzzle/src/ctrl.ts @@ -50,7 +50,7 @@ export default function (opts: PuzzleOpts, redraw: Redraw): Controller { const throttleSound = (name: string) => throttle(100, () => lichess.sound.play(name)); const loadSound = (file: string, volume?: number, delay?: number) => { - setTimeout(() => lichess.sound.loadOggOrMp3(file, `${lichess.sound.baseUrl}/${file}`), delay || 1000); + setTimeout(() => lichess.sound.loadOggOrMp3(file, `${lichess.sound.baseUrl}/${file}`, true), delay || 1000); return () => lichess.sound.play(file, volume); }; const sound = { diff --git a/ui/simul/css/_show.scss b/ui/simul/css/_show.scss index d904128896..c376ab5d69 100644 --- a/ui/simul/css/_show.scss +++ b/ui/simul/css/_show.scss @@ -10,11 +10,16 @@ $box-padding: var(--box-padding); @import 'started'; .simul { + @include breakpoint($mq-xx-small) { + .box__top { + flex-wrap: nowrap; + } + } + h1 { font-size: 2.1em; .author { - margin-left: 0.7em; font-size: 0.7em; } } diff --git a/ui/simul/src/view/created.ts b/ui/simul/src/view/created.ts index 7d5976ad3e..5439976e50 100644 --- a/ui/simul/src/view/created.ts +++ b/ui/simul/src/view/created.ts @@ -1,12 +1,12 @@ -import { h, VNode } from 'snabbdom'; -import { bind } from 'common/snabbdom'; +import { h } from 'snabbdom'; +import { bind, MaybeVNode } from 'common/snabbdom'; import SimulCtrl from '../ctrl'; import { Applicant } from '../interfaces'; import xhr from '../xhr'; import * as util from './util'; import modal from 'common/modal'; -export default function (showText: (ctrl: SimulCtrl) => VNode) { +export default function (showText: (ctrl: SimulCtrl) => MaybeVNode) { return (ctrl: SimulCtrl) => { const candidates = ctrl.candidates().sort(byName), accepted = ctrl.accepted().sort(byName), diff --git a/ui/simul/src/view/main.ts b/ui/simul/src/view/main.ts index b26227f2fd..eb390ca55a 100644 --- a/ui/simul/src/view/main.ts +++ b/ui/simul/src/view/main.ts @@ -46,11 +46,13 @@ export default function (ctrl: SimulCtrl) { } const showText = (ctrl: SimulCtrl) => - h('div.simul-text', [ - h('p', { - hook: richHTML(ctrl.data.text), - }), - ]); + ctrl.data.text.length > 0 + ? h('div.simul-text', [ + h('p', { + hook: richHTML(ctrl.data.text), + }), + ]) + : undefined; const started = (ctrl: SimulCtrl) => [util.title(ctrl), showText(ctrl), results(ctrl), pairings(ctrl)]; diff --git a/ui/simul/src/view/util.ts b/ui/simul/src/view/util.ts index 5dff403a04..442da300c5 100644 --- a/ui/simul/src/view/util.ts +++ b/ui/simul/src/view/util.ts @@ -24,4 +24,4 @@ export function player(p: Player, ctrl: SimulCtrl) { const userName = (u: LightUser) => (u.title ? [h('span.utitle', u.title), ' ' + u.name] : [u.name]); export const title = (ctrl: SimulCtrl) => - h('h1', [ctrl.data.fullName, h('span.author', ctrl.trans.vdom('by', player(ctrl.data.host, ctrl)))]); + h('h1', [ctrl.data.fullName, h('br'), h('span.author', ctrl.trans.vdom('by', player(ctrl.data.host, ctrl)))]); diff --git a/ui/site/css/_mobile.scss b/ui/site/css/_mobile.scss index 4dec9a9275..68bedd58fc 100644 --- a/ui/site/css/_mobile.scss +++ b/ui/site/css/_mobile.scss @@ -40,9 +40,14 @@ } .right-side { - flex: 0 0 264px; + flex: 0 0 33%; text-align: center; + .mobile-playing { + width: 100%; + height: auto; + } + .qrcode { margin-top: 20px; } diff --git a/ui/site/css/user/_activity.scss b/ui/site/css/user/_activity.scss index 2a109e37b2..3687142925 100644 --- a/ui/site/css/user/_activity.scss +++ b/ui/site/css/user/_activity.scss @@ -5,6 +5,10 @@ $c-contours: mix($c-brag, $c-shade, 80%); font-size: 1.2em; margin-left: 2rem; padding-top: 1rem; + + @include breakpoint($mq-not-small) { + margin-right: 2em; + } } section h2 { diff --git a/ui/site/src/component/sound.ts b/ui/site/src/component/sound.ts index 7ed9d7cdf0..07793ebef1 100644 --- a/ui/site/src/component/sound.ts +++ b/ui/site/src/component/sound.ts @@ -18,7 +18,8 @@ type Name = string; type Path = string; const sound: SoundI = new (class { - sounds = new Map(); // The loaded sounds and their instances + soundSetSounds = new Map(); // The loaded sounds and their instances + standaloneSounds = new Map(); // Sounds that are independent of the sound set soundSet = $('body').data('sound-set'); speechStorage = storage.makeBoolean('speech.enabled'); volumeStorage = storage.make('sound-volume'); @@ -30,8 +31,8 @@ const sound: SoundI = new (class { if (this.soundSet == 'music') setTimeout(this.publish, 500); } - loadOggOrMp3 = (name: Name, path: Path) => - this.sounds.set( + loadOggOrMp3 = (name: Name, path: Path, noSoundSet = false) => + (noSoundSet ? this.standaloneSounds : this.soundSetSounds).set( name, new Howl({ src: ['ogg', 'mp3'].map(ext => `${path}.${ext}`), @@ -49,10 +50,10 @@ const sound: SoundI = new (class { } private getOrLoadSound = (name: string, set: string): Howl => { - let s = this.sounds.get(name); + let s = this.soundSetSounds.get(name) ?? this.standaloneSounds.get(name); if (!s) { this.loadStandard(name, set); - s = this.sounds.get(name)!; + s = this.soundSetSounds.get(name)!; } return s; }; @@ -115,7 +116,7 @@ const sound: SoundI = new (class { changeSet = (s: string) => { this.soundSet = s; - this.sounds.clear(); + this.soundSetSounds.clear(); this.publish(); }; diff --git a/ui/site/src/component/spinner.ts b/ui/site/src/component/spinner.ts index 83da995a81..708f40f581 100644 --- a/ui/site/src/component/spinner.ts +++ b/ui/site/src/component/spinner.ts @@ -1,4 +1,4 @@ const spinner = - '
'; + '
'; export default spinner; diff --git a/ui/site/src/teamAdmin.ts b/ui/site/src/teamAdmin.ts index c6e83d22c9..0e4970023b 100644 --- a/ui/site/src/teamAdmin.ts +++ b/ui/site/src/teamAdmin.ts @@ -12,12 +12,17 @@ lichess.load.then(() => { }); function initTagify(input: HTMLInputElement, maxTags: number) { - const team = input.dataset['rel']; + const team = input.dataset.rel; const tagify = new Tagify(input, { pattern: /.{3,}/, maxTags, - enforceWhitelist: true, - whitelist: input.value.trim().split(/\s*,\s*/), + whitelist: [], + hooks: { + beforePaste: (_, data) => { + data.tagify.settings.enforceWhitelist = false; + return Promise.resolve(undefined); + }, + }, }); const doFetch: (term: string) => Promise = debounce( (term: string) => xhr.json(xhr.url('/player/autocomplete', { term, names: 1, team })), @@ -26,11 +31,12 @@ function initTagify(input: HTMLInputElement, maxTags: number) { tagify.on('input', e => { const term = e.detail.value.trim(); if (term.length < 3) return; - tagify.settings.whitelist!.length = 0; // reset the whitelist + tagify.whitelist = []; + tagify.settings.enforceWhitelist = true; // show loading animation and hide the suggestions dropdown tagify.loading(true).dropdown.hide.call(tagify); doFetch(term).then((list: string[]) => { - tagify.settings.whitelist!.splice(0, list.length, ...list); // update whitelist Array in-place + tagify.whitelist = list; tagify.loading(false).dropdown.show.call(tagify, term); // render the suggestions dropdown }); }); diff --git a/ui/site/src/ublog.ts b/ui/site/src/ublog.ts index 6107c680be..13bcabcad4 100644 --- a/ui/site/src/ublog.ts +++ b/ui/site/src/ublog.ts @@ -14,8 +14,11 @@ lichess.load.then(() => { method: 'post', }) .then(likes => { + const label = $('.ublog-post__like .button-label'); + const newText = label.data(`i18n-${liked ? 'unlike' : 'like'}`); + label.text(newText); + $('.ublog-post__like').toggleClass(likeClass, liked).attr('title', newText); $('.ublog-post__like__nb').text(likes); - $('.ublog-post__like').toggleClass(likeClass, liked); }); }) ); diff --git a/ui/site/src/userGamesDownload.ts b/ui/site/src/userGamesDownload.ts index cdc4d04efd..81b93b16a6 100644 --- a/ui/site/src/userGamesDownload.ts +++ b/ui/site/src/userGamesDownload.ts @@ -9,8 +9,8 @@ function generateSearchParams(): string { }); ['since', 'until'].forEach(name => { - const minTimestamp = 1356998400070; - const date = $(`#dl-date-${name}`).val() as string; + const minTimestamp = 1356998400070; // 01/01/2013, 01:00:00 + const date = ($(`#dl-date-${name}`).val() as string).replace(/-/g, '/'); // for Safari https://stackoverflow.com/a/4310986/11955835 const time = $(`#dl-time-${name}`).val() as string; if (date.length == 10) { // the 00:00:00 is necessary for the time to be interpreted in the local timezone