diff --git a/app/controllers/Account.scala b/app/controllers/Account.scala index 938b0c03b1371..3802e73ba49f3 100644 --- a/app/controllers/Account.scala +++ b/app/controllers/Account.scala @@ -102,6 +102,7 @@ final class Account( me.value, withFollows = apiC.userWithFollows, withTrophies = false, + withCanChallenge = false, forWiki = wikiGranted ) .dmap { JsonOk(_) } diff --git a/app/controllers/Api.scala b/app/controllers/Api.scala index 17947ad1ffa0c..f6c3ad921adb0 100644 --- a/app/controllers/Api.scala +++ b/app/controllers/Api.scala @@ -47,7 +47,8 @@ final class Api( .extended( name, withFollows = userWithFollows, - withTrophies = getBool("trophies") + withTrophies = getBool("trophies"), + withCanChallenge = getBool("challenge") ) .map(toApiResult) .map(toHttp) diff --git a/app/controllers/Auth.scala b/app/controllers/Auth.scala index 2a64071ea7e71..c9370ddbba3f8 100644 --- a/app/controllers/Auth.scala +++ b/app/controllers/Auth.scala @@ -40,12 +40,11 @@ final class Auth( ): Fu[Result] = api .saveAuthentication(u.id, ctx.mobileApiVersion) - .flatMap { sessionId => + .flatMap: sessionId => negotiate( result.fold(Redirect(getReferrer))(_(getReferrer)), mobileUserOk(u, sessionId) ).map(authenticateCookie(sessionId, remember)) - } .recoverWith(authRecovery) private def authenticateAppealUser(u: UserModel, redirect: String => Result)(using @@ -53,10 +52,9 @@ final class Auth( ): Fu[Result] = api.appeal .saveAuthentication(u.id) - .flatMap { sessionId => + .flatMap: sessionId => authenticateCookie(sessionId, remember = false): redirect(routes.Appeal.landing.url) - } .recoverWith(authRecovery) private def authenticateCookie(sessionId: String, remember: Boolean)( diff --git a/app/controllers/BulkPairing.scala b/app/controllers/BulkPairing.scala index e12c1a1dbd745..7abc651851961 100644 --- a/app/controllers/BulkPairing.scala +++ b/app/controllers/BulkPairing.scala @@ -5,8 +5,9 @@ import play.api.libs.json.* import lila.app.* import lila.common.Json.given import lila.challenge.ChallengeBulkSetup +import lila.api.GameApiV2 -final class BulkPairing(env: Env) extends LilaController(env): +final class BulkPairing(gameC: => Game, apiC: => Api, env: Env) extends LilaController(env): def list = ScopedBody(_.Challenge.Bulk) { _ ?=> me ?=> env.challenge.bulk @@ -23,6 +24,24 @@ final class BulkPairing(env: Env) extends LilaController(env): JsonOk(ChallengeBulkSetup.toJson(bulk)) } + def games(id: String) = ScopedBody(_.Challenge.Bulk) { _ ?=> me ?=> + env.challenge.bulk + .findBy(id, me) + .map: + _.fold(notFoundText()): bulk => + val config = GameApiV2.ByIdsConfig( + ids = bulk.games.map(_.id), + format = GameApiV2.Format.byRequest(req), + flags = gameC + .requestPgnFlags(extended = false) + .copy(delayMoves = false), + perSecond = MaxPerSecond(50) + ) + apiC.GlobalConcurrencyLimitPerIP + .download(req.ipAddress)(env.api.gameApiV2.exportByIds(config)): source => + noProxyBuffer(Ok.chunked(source)).as(gameC.gameContentType(config)) + } + def delete(id: String) = ScopedBody(_.Challenge.Bulk) { _ ?=> me ?=> env.challenge.bulk .deleteBy(id, me) diff --git a/app/controllers/Challenge.scala b/app/controllers/Challenge.scala index 4c4e8fffbcee3..070426baceb29 100644 --- a/app/controllers/Challenge.scala +++ b/app/controllers/Challenge.scala @@ -274,7 +274,7 @@ final class Challenge( case None => redir case Some(dest) if ctx.is(dest) => redir case Some(dest) => - env.challenge.granter.isDenied(dest, c.perfType).flatMap { + env.challenge.granter.isDenied(dest, c.perfType.key.some).flatMap { case Some(denied) => showChallenge(c, lila.challenge.ChallengeDenied.translated(denied).some) case None => api.setDestUser(c, dest).inject(redir) @@ -303,7 +303,7 @@ final class Challenge( limit.challengeUser(me, rateLimited, cost = cost): for challenge <- makeOauthChallenge(config, me, destUser) - grant <- env.challenge.granter.isDenied(destUser, config.perfType) + grant <- env.challenge.granter.isDenied(destUser, config.perfKey.some) res <- grant match case Some(denied) => fuccess: @@ -376,7 +376,7 @@ final class Challenge( NoBot: Found(env.game.gameRepo.game(gameId)): g => g.opponentOf(me).flatMap(_.userId).so(env.user.repo.byId).orNotFound { opponent => - env.challenge.granter.isDenied(opponent, g.perfKey).flatMap { + env.challenge.granter.isDenied(opponent, g.perfKey.some).flatMap { case Some(d) => BadRequest(jsonError(lila.challenge.ChallengeDenied.translated(d))) case _ => api.offerRematchForGame(g, me).map { diff --git a/app/controllers/ForumCateg.scala b/app/controllers/ForumCateg.scala index b960398dd7962..cc62a504da88c 100644 --- a/app/controllers/ForumCateg.scala +++ b/app/controllers/ForumCateg.scala @@ -35,3 +35,12 @@ final class ForumCateg(env: Env) extends LilaController(env) with ForumControlle if canRead then Ok.page(views.forum.categ.show(categ, topics, canWrite, stickyPosts)) else notFound yield res + + def modFeed(slug: ForumCategId, page: Int) = Secure(_.ModerateForum) { ctx ?=> _ ?=> + Found(env.forum.categRepo.byId(slug)): categ => + for + posts <- env.forum.paginator.recent(categ, page) + postViews <- posts.mapFutureList(env.forum.postApi.views) + page <- renderPage(views.forum.categ.modFeed(categ, postViews)) + yield Ok(page) + } diff --git a/app/controllers/ForumPost.scala b/app/controllers/ForumPost.scala index 24216c5853b28..3661e13cfc796 100644 --- a/app/controllers/ForumPost.scala +++ b/app/controllers/ForumPost.scala @@ -108,6 +108,21 @@ final class ForumPost(env: Env) extends LilaController(env) with ForumController NoContent } + def relocate(id: ForumPostId) = SecureBody(_.ModerateForum) { ctx ?=> me ?=> + Found(postApi.getPost(id).flatMapz(postApi.viewOf)): post => + forms.relocateTo + .bindFromRequest() + .value + .so: to => + env.forum.topicApi + .relocate(post.topic.id, to) + .inject: + post.post.userId.foreach: op => + val newUrl = routes.ForumTopic.show(to, post.topic.slug, 1).url + env.msg.api.systemPost(op, MsgPreset.forumRelocation(post.topic.name, newUrl)) + Redirect(routes.ForumCateg.show(to)).flashSuccess + } + def react(categId: ForumCategId, id: ForumPostId, reaction: String, v: Boolean) = Auth { _ ?=> me ?=> CategGrantWrite(categId): FoundSnip(postApi.react(categId, id, reaction, v)): post => diff --git a/app/controllers/Mod.scala b/app/controllers/Mod.scala index 0499854ac93ac..79bf7ccc98542 100644 --- a/app/controllers/Mod.scala +++ b/app/controllers/Mod.scala @@ -159,16 +159,14 @@ final class Mod( bindForm(lila.user.UserForm.title)( _ => redirect(username, mod = true), title => - doSetTitle(username.id, title, public = true).inject: + doSetTitle(username.id, title).inject: redirect(username, mod = false) ) } - protected[controllers] def doSetTitle(userId: UserId, title: Option[chess.PlayerTitle], public: Boolean)( - using Me - ) = for - _ <- (public || title.isEmpty).so(modApi.setTitle(userId, title)) - _ <- title.so(env.mailer.automaticEmail.onTitleSet(userId, _, public)) + protected[controllers] def doSetTitle(userId: UserId, title: Option[chess.PlayerTitle])(using Me) = for + _ <- modApi.setTitle(userId, title) + _ <- title.so(env.mailer.automaticEmail.onTitleSet(userId, _)) yield () def setEmail(username: UserStr) = SecureBody(_.SetEmail) { ctx ?=> me ?=> @@ -470,7 +468,7 @@ final class Mod( } >> { Permission .ofDbKeys(permissions) - .exists(_.grants(Permission.SeeReport)) + .exists(p => p.grants(Permission.SeeReport) || p.grants(Permission.Developer)) .so(env.plan.api.setLifetime(user)) }).inject(Redirect(routes.Mod.permissions(user.username)).flashSuccess) ) diff --git a/app/controllers/RelayRound.scala b/app/controllers/RelayRound.scala index 57586cd408e68..a6e3748d3f3be 100644 --- a/app/controllers/RelayRound.scala +++ b/app/controllers/RelayRound.scala @@ -94,9 +94,9 @@ final class RelayRound( ) } - def reset(id: RelayRoundId) = Auth { ctx ?=> me ?=> + def reset(id: RelayRoundId) = AuthOrScoped(_.Study.Write) { ctx ?=> me ?=> Found(env.relay.api.byIdAndContributor(id)): rt => - env.relay.api.reset(rt.round).inject(Redirect(rt.path)) + env.relay.api.reset(rt.round) >> negotiate(Redirect(rt.path), jsonOkResult) } def show(ts: String, rs: String, id: RelayRoundId, embed: Option[UserStr]) = @@ -122,13 +122,29 @@ final class RelayRound( Found(env.study.studyRepo.byId(rt.round.studyId)): study => studyC.CanView(study)( for - group <- env.relay.api.withTours.get(rt.tour.id) - previews <- env.study.preview.jsonList(study.id) - yield JsonOk(env.relay.jsonView.withUrlAndPreviews(rt.withStudy(study), previews, group)) + group <- env.relay.api.withTours.get(rt.tour.id) + previews <- env.study.preview.jsonList.withoutInitialEmpty(study.id) + targetRound <- env.relay.api.officialTarget(rt.round) + yield JsonOk( + env.relay.jsonView.withUrlAndPreviews(rt.withStudy(study), previews, group, targetRound) + ) )(studyC.privateUnauthorizedJson, studyC.privateForbiddenJson) - def pgn(ts: String, rs: String, id: StudyId) = studyC.pgn(id) - def apiPgn = studyC.apiPgn + def pgn(ts: String, rs: String, id: RelayRoundId) = Open: + pgnWithFlags(ts, rs, id) + + def apiPgn(id: RelayRoundId) = AnonOrScoped(_.Study.Read): ctx ?=> + pgnWithFlags("-", "-", id) + + private def pgnWithFlags(ts: String, rs: String, id: RelayRoundId)(using Context): Fu[Result] = + studyC.pgnWithFlags( + id.into(StudyId), + _.copy( + site = s"${env.net.baseUrl}${routes.RelayRound.show(ts, rs, id)}".some, + comments = false, + variations = false + ) + ) def apiMyRounds = Scoped(_.Study.Read) { ctx ?=> _ ?=> val source = env.relay.api.myRounds(MaxPerSecond(20), getIntAs[Max]("nb")).map(env.relay.jsonView.myRound) @@ -174,6 +190,13 @@ final class RelayRound( env.relay.teamTable.tableJson(rt.relay).map(JsonStrOk) }(Unauthorized, Forbidden) + def stats(id: RelayRoundId) = Open: + env.relay.stats + .get(id) + .map: stats => + import lila.relay.JsonView.given + JsonOk(stats) + private def WithRoundAndTour(@nowarn ts: String, @nowarn rs: String, id: RelayRoundId)( f: RoundModel.WithTour => Fu[Result] )(using ctx: Context): Fu[Result] = diff --git a/app/controllers/RelayTour.scala b/app/controllers/RelayTour.scala index 1ea846a2b2e83..c04b581aa592a 100644 --- a/app/controllers/RelayTour.scala +++ b/app/controllers/RelayTour.scala @@ -115,7 +115,7 @@ final class RelayTour(env: Env, apiC: => Api) extends LilaController(env): setup => env.relay.api.tourUpdate(nav.tour, setup) >> negotiate( - Redirect(routes.RelayTour.show(nav.tour.slug, nav.tour.id)), + Redirect(routes.RelayTour.edit(nav.tour.id)).flashSuccess, jsonOkResult ) ) @@ -189,13 +189,6 @@ final class RelayTour(env: Env, apiC: => Api) extends LilaController(env): asAttachmentStream(s"${env.relay.pgnStream.filename(tour)}.pgn"): Ok.chunked(source).as(pgnContentType) - def stats(id: RelayTourId) = Open: - Found(env.relay.api.tourById(id)): tour => - env.relay.stats - .get(tour.id) - .flatMap: stats => - Ok.page(views.relay.tour.stats(tour, stats)) - def apiIndex = Anon: apiC.jsonDownload: env.relay.tourStream diff --git a/app/controllers/Report.scala b/app/controllers/Report.scala index e6de5e57dec2a..eb738613ea763 100644 --- a/app/controllers/Report.scala +++ b/app/controllers/Report.scala @@ -153,7 +153,7 @@ final class Report(env: Env, userC: => User, modC: => Mod) extends LilaControlle text = s"$pid\n\n" ) case _ => form - views.report.form(filledForm, user) + views.report.ui.form(filledForm, user) } } @@ -162,7 +162,7 @@ final class Report(env: Env, userC: => User, modC: => Mod) extends LilaControlle err => for user <- getUserStr("username").so(env.user.repo.byId) - page <- renderPage(views.report.form(err, user)) + page <- renderPage(views.report.ui.form(err, user)) yield BadRequest(page), data => if me.is(data.user.id) then BadRequest("You cannot report yourself") diff --git a/app/controllers/Setup.scala b/app/controllers/Setup.scala index 23125ee8e9074..62c5d85c372c1 100644 --- a/app/controllers/Setup.scala +++ b/app/controllers/Setup.scala @@ -48,7 +48,7 @@ final class Setup( for origUser <- ctx.user.soFu(env.user.perfsRepo.withPerf(_, config.perfType)) destUser <- userId.so(env.user.api.enabledWithPerf(_, config.perfType)) - denied <- destUser.so(u => env.challenge.granter.isDenied(u.user, config.perfType)) + denied <- destUser.so(u => env.challenge.granter.isDenied(u.user, config.perfKey.some)) result <- denied match case Some(denied) => val message = lila.challenge.ChallengeDenied.translated(denied) diff --git a/app/controllers/Study.scala b/app/controllers/Study.scala index 13d7f269a664c..f911b5ce423da 100644 --- a/app/controllers/Study.scala +++ b/app/controllers/Study.scala @@ -15,6 +15,7 @@ import lila.study.JsonView.JsData import lila.study.Study.WithChapter import lila.study.actorApi.{ BecomeStudyAdmin, Who } import lila.study.{ Chapter, Settings, Orders, Study as StudyModel, StudyForm } +import lila.study.PgnDump.WithFlags import lila.tree.Node.partitionTreeJsonWriter import lila.core.misc.lpv.LpvEmbed import lila.core.net.IpAddress @@ -242,7 +243,7 @@ final class Study( division = division ) ) - withMembers = !study.isRelay || isGrantedOpt(_.StudyAdmin) + withMembers = !study.isRelay || isGrantedOpt(_.StudyAdmin) || ctx.me.exists(study.isMember) studyJson <- env.study.jsonView(study, previews, chapter, fedNames.some, withMembers = withMembers) yield WithChapter(study, chapter) -> JsData( study = studyJson, @@ -319,11 +320,12 @@ final class Study( def delete(id: StudyId) = Auth { _ ?=> me ?=> Found(env.study.api.byIdAndOwnerOrAdmin(id, me)): study => - env.study.api.delete(study) >> env.relay.api - .deleteRound(id.into(RelayRoundId)) - .map: - case None => Redirect(routes.Study.mine(Order.hot)) - case Some(tour) => Redirect(routes.RelayTour.show(tour.slug, tour.id)) + for + round <- env.relay.api.deleteRound(id.into(RelayRoundId)) + _ <- env.study.api.delete(study) + yield round match + case None => Redirect(routes.Study.mine(Order.hot)) + case Some(tour) => Redirect(routes.RelayTour.show(tour.slug, tour.id)) } def apiChapterDelete(id: StudyId, chapterId: StudyChapterId) = ScopedBody(_.Study.Write) { _ ?=> me ?=> @@ -411,27 +413,23 @@ final class Study( } def pgn(id: StudyId) = Open: + pgnWithFlags(id, identity) + + def apiPgn(id: StudyId) = AnonOrScoped(_.Study.Read): ctx ?=> + pgnWithFlags(id, identity) + + def pgnWithFlags(id: StudyId, flags: Update[WithFlags])(using Context) = Found(env.study.api.byId(id)): study => HeadLastModifiedAt(study.updatedAt): - limit.studyPgn(ctx.ip, rateLimited, msg = id.value): - CanView(study, study.settings.shareable.some)(doPgn(study))( + val limiter = if study.isRelay then limit.relayPgn else limit.studyPgn + limiter[Fu[Result]](req.ipAddress, rateLimited, msg = id.value): + CanView(study, study.settings.shareable.some)(doPgn(study, flags))( privateUnauthorizedFu(study), privateForbiddenFu(study) ) - def apiPgn(id: StudyId) = AnonOrScoped(_.Study.Read): ctx ?=> - env.study.api.byId(id).flatMap { - _.fold(studyNotFoundText.toFuccess): study => - HeadLastModifiedAt(study.updatedAt): - limit.studyPgn[Fu[Result]](req.ipAddress, rateLimited, msg = id.value): - CanView(study, study.settings.shareable.some)(doPgn(study))( - privateUnauthorizedText, - privateForbiddenText - ) - } - - private def doPgn(study: StudyModel)(using RequestHeader, Option[Me]) = - Ok.chunked(env.study.pgnDump.chaptersOf(study, requestPgnFlags).throttle(16, 1.second)) + private def doPgn(study: StudyModel, flags: Update[WithFlags])(using RequestHeader, Option[Me]) = + Ok.chunked(env.study.pgnDump.chaptersOf(study, flags(requestPgnFlags)).throttle(20, 1.second)) .pipe(asAttachmentStream(s"${env.study.pgnDump.filename(study)}.pgn")) .as(pgnContentType) .withDateHeaders(lastModified(study.updatedAt)) @@ -494,12 +492,13 @@ final class Study( .map(lila.study.JsonView.metadata) private def requestPgnFlags(using RequestHeader) = - lila.study.PgnDump.WithFlags( + WithFlags( comments = getBoolOpt("comments") | true, variations = getBoolOpt("variations") | true, clocks = getBoolOpt("clocks") | true, source = getBool("source"), - orientation = getBool("orientation") + orientation = getBool("orientation"), + site = none ) def chapterGif(id: StudyId, chapterId: StudyChapterId, theme: Option[String], piece: Option[String]) = Open: diff --git a/app/controllers/TeamApi.scala b/app/controllers/TeamApi.scala index ca55d42d0f963..32f961d5bc1c4 100644 --- a/app/controllers/TeamApi.scala +++ b/app/controllers/TeamApi.scala @@ -50,9 +50,10 @@ final class TeamApi(env: Env, apiC: => Api) extends LilaController(env): else ctx.me.so(api.belongsTo(team.id, _)) canView.map: if _ then + val full = getBool("full") apiC.jsonDownload( env.team - .memberStream(team, MaxPerSecond(20)) + .memberStream(team, full) .map: (user, joinedAt) => env.api.userApi.one(user, joinedAt.some) ) diff --git a/app/controllers/TitleVerify.scala b/app/controllers/TitleVerify.scala index 68f89b6057368..ef1ea5b1e345b 100644 --- a/app/controllers/TitleVerify.scala +++ b/app/controllers/TitleVerify.scala @@ -123,10 +123,12 @@ final class TitleVerify(env: Env, cmsC: => Cms, reportC: => report.Report, userC private def onApproved(req: TitleRequest)(using Context, Me) = for user <- env.user.api.byId(req.userId).orFail(s"User ${req.userId} not found") - _ <- modC.doSetTitle(user.id, req.data.title.some, req.data.public) + _ <- modC.doSetTitle(user.id, req.data.title.some) url = s"${env.net.baseUrl}${routes.TitleVerify.show(req.id)}" note = s"Title verified: ${req.data.title}. Public: ${if req.data.public then "Yes" else "No"}. $url" _ <- env.user.noteApi.write(user.id, note, modOnly = true, dox = false) + _ <- req.data.public.so: + env.user.repo.setRealName(user.id, req.data.realName) _ <- req.data.coach.so: env.user.repo.addPermission(user.id, lila.core.perm.Permission.Coach) _ <- req.data.coach.so: diff --git a/app/controllers/User.scala b/app/controllers/User.scala index 86e0a888576aa..5d5322282509e 100644 --- a/app/controllers/User.scala +++ b/app/controllers/User.scala @@ -66,6 +66,12 @@ final class User( apiGames(u, GameFilter.All.name, 1) ) + def search(term: String) = Open: _ ?=> + UserStr.read(term) match + case Some(username) => Redirect(routes.User.show(username)).toFuccess + case _ if isGrantedOpt(_.UserSearch) => Redirect(s"${routes.Mod.search}?q=$term").toFuccess + case _ => notFound + private def renderShow(u: UserModel, status: Results.Status = Results.Ok)(using Context): Fu[Result] = if HTTPRequest.isSynchronousHttp(ctx.req) then diff --git a/app/http/CtrlFilters.scala b/app/http/CtrlFilters.scala index 1bf518af10d08..5259d05c84cc6 100644 --- a/app/http/CtrlFilters.scala +++ b/app/http/CtrlFilters.scala @@ -104,7 +104,7 @@ trait CtrlFilters(using Executor) extends ControllerHelpers with ResponseBuilder max: Max = Max(40), errorPage: => Fu[Result] = BadRequest("resource too old") )(result: => Fu[Result]): Fu[Result] = - if page < max.value && page > 0 then result else errorPage + if page <= max.value && page > 0 then result else errorPage def NotForKids(f: => Fu[Result])(using ctx: Context): Fu[Result] = if ctx.kid.no then f else notFound diff --git a/app/views/report.scala b/app/views/report.scala index 7d3d0f771465e..0ee10a7f127cd 100644 --- a/app/views/report.scala +++ b/app/views/report.scala @@ -160,78 +160,3 @@ def layout(filter: String, scores: Room.Scores, pending: PendingCounts)(using body ) ) - -def form(form: Form[?], reqUser: Option[User] = None)(using ctx: Context) = - Page(trans.site.reportAUser.txt()) - .css("bits.form3") - .js( - embedJsUnsafeLoadThen( - """$('#form3-reason').on('change', function() { - $('.report-reason').addClass('none').filter('.report-reason-' + this.value).removeClass('none'); - })""" - ) - ): - val defaultReason = form("reason").value.orElse(translatedReasonChoices.headOption.map(_._1)) - main(cls := "page-small box box-pad report")( - h1(cls := "box__top")(trans.site.reportAUser()), - postForm( - cls := "form3", - action := s"${routes.Report.create}${reqUser.so(u => "?username=" + u.username)}" - )( - div(cls := "form-group")( - p( - a( - href := routes.Cms.lonePage(lila.core.id.CmsPageKey("report-faq")), - dataIcon := Icon.InfoCircle, - cls := "text" - ): - "Read more about Lichess reports" - ), - ctx.req.queryString - .contains("postUrl") - .option( - p( - "Here for DMCA or Intellectual Property Take Down Notice? ", - a(href := lila.web.ui.contact.dmcaUrl)("Complete this form instead"), - "." - ) - ) - ), - form3.globalError(form), - form3.group(form("username"), trans.site.user(), klass = "field_to complete-parent"): f => - reqUser - .map: user => - frag(userLink(user), form3.hidden(f, user.id.value.some)) - .getOrElse: - div(form3.input(f, klass = "user-autocomplete")(dataTag := "span", autofocus)) - , - if ctx.req.queryString contains "reason" - then form3.hidden(form("reason")) - else - form3.group(form("reason"), trans.site.reason()): f => - form3.select(f, translatedReasonChoices, trans.site.whatIsIheMatter.txt().some) - , - form3.group(form("text"), trans.site.description(), help = descriptionHelp(~defaultReason).some): - form3.textarea(_)(rows := 8) - , - form3.actions( - a(href := routes.Lobby.home)(trans.site.cancel()), - form3.submit(trans.site.send()) - ) - ) - ) - -private def descriptionHelp(default: String)(using ctx: Context) = frag: - import lila.report.Reason.* - val englishPlease = " Your report will be processed faster if written in English." - translatedReasonChoices - .map(_._1) - .distinct - .map: key => - span(cls := List(s"report-reason report-reason-$key" -> true, "none" -> (default != key))): - if key == Cheat.key || key == Boost.key then trans.site.reportDescriptionHelp() - else if key == Username.key then - "Please explain briefly what about this username is offensive." + englishPlease - else if key == Comm.key || key == Sexism.key then - "Please explain briefly what that user said that was abusive." + englishPlease - else "Please explain briefly what happened." + englishPlease diff --git a/app/views/title.scala b/app/views/title.scala index b6659d0ed035d..49a611075dbc5 100644 --- a/app/views/title.scala +++ b/app/views/title.scala @@ -34,7 +34,8 @@ object mod: " ", player.name ), - p(player.ratingsStr) + p(player.ratingsStr), + p("Year of birth: ", player.year.fold("unknown")(_.toString)) ) modUi.show(req, data.user, fide, similar, modZone) diff --git a/bin/mongodb/indexes.js b/bin/mongodb/indexes.js index d8cd1e8f29262..9c0fe308eba62 100644 --- a/bin/mongodb/indexes.js +++ b/bin/mongodb/indexes.js @@ -202,6 +202,10 @@ db.relay.createIndex({ startsAt: 1 }, { partialFilterExpression: { startsAt: { $ db.relay.createIndex({ startedAt: 1 }, { partialFilterExpression: { startedAt: { $exists: 1 } } }); db.relay.createIndex({ 'sync.until': 1 }, { partialFilterExpression: { 'sync.until': { $exists: 1 } } }); db.relay.createIndex({ tourId: 1 }); +db.relay.createIndex( + { 'sync.upstream.roundIds': 1 }, + { partialFilterExpression: { 'sync.upstream.roundIds': { $exists: 1 } } }, +); db.oauth2_access_token.createIndex({ userId: 1 }); db.oauth2_access_token.createIndex({ expires: 1 }, { expireAfterSeconds: 0 }); db.cache.createIndex({ e: 1 }, { expireAfterSeconds: 0 }); diff --git a/bin/mongodb/real-name-migrate.js b/bin/mongodb/real-name-migrate.js new file mode 100644 index 0000000000000..77c442073b87f --- /dev/null +++ b/bin/mongodb/real-name-migrate.js @@ -0,0 +1,17 @@ +const sel = { $or: [{ 'profile.firstName': { $exists: true } }, { 'profile.lastName': { $exists: true } }] }; + +db.user4 + .find(sel, { profile: 1 }) + .limit(1000) + .forEach(function (user) { + const fullName = ((user.profile.firstName || '') + ' ' + (user.profile.lastName || '')) + .trim() + .replace(/\s+/g, ' '); + db.user4.updateOne( + { _id: user._id }, + { + $set: { 'profile.realName': fullName }, + $unset: { 'profile.firstName': true, 'profile.lastName': true }, + }, + ); + }); diff --git a/bin/mongodb/relay-dates-migrate.js b/bin/mongodb/relay-dates-migrate.js new file mode 100644 index 0000000000000..93b78b7fa628f --- /dev/null +++ b/bin/mongodb/relay-dates-migrate.js @@ -0,0 +1,32 @@ +// db.relay.aggregate([{$match:{tourId:'KNfeoWE'}},{$project:{name:1,at:{$ifNull:['$startsAt','$startedAt']}}},{$sort:{at:1}},{$group:{_id:null,at:{$push:'$at'}}},{$project:{start:{$first:'$at'},end:{$last:'$at'}}}]) + +const fetchDates = tourId => + db.relay + .aggregate([ + { $match: { tourId } }, + { $project: { name: 1, at: { $ifNull: ['$startsAt', '$startedAt'] } } }, + { $sort: { at: 1 } }, + { $group: { _id: null, at: { $push: '$at' } } }, + { $project: { start: { $first: '$at' }, end: { $last: '$at' } } }, + ]) + .next(); + +const cmp = (a, b) => (a ? a.getTime() : 0) == (b ? b.getTime() : 0); + +db.relay_tour + .find() + .sort({ $natural: -1 }) + .limit(200) + .forEach(tour => { + const dates = fetchDates(tour._id); + if (dates) { + if (!cmp(dates?.start, tour.dates?.start)) { + console.log(tour._id + ' ' + tour.dates?.start + ' -> ' + dates.start); + db.relay_tour.updateOne({ _id: tour._id }, { $set: { 'dates.start': dates.start } }); + } + if (!cmp(dates?.end, tour.dates?.end)) { + console.log(tour._id + ' ' + tour.dates?.end + ' -> ' + dates.end); + db.relay_tour.updateOne({ _id: tour._id }, { $set: { 'dates.end': dates.end } }); + } + } + }); diff --git a/bin/mongodb/relay-lcc-migrate-2.js b/bin/mongodb/relay-lcc-migrate-2.js new file mode 100644 index 0000000000000..f6ba297a92817 --- /dev/null +++ b/bin/mongodb/relay-lcc-migrate-2.js @@ -0,0 +1,31 @@ +const lccUrl = (id, round) => `https://view.livechesscloud.com/#${id}/${round}`; + +db.relay + .find({ 'sync.upstream.lcc': { $exists: 1 } }) + .sort({ $natural: -1 }) + .forEach(relay => { + db.relay.updateOne( + { _id: relay._id }, + { + $set: { + 'sync.upstream': { url: lccUrl(relay.sync.upstream.lcc, relay.sync.upstream.round) }, + }, + }, + ); + }); + +db.relay + .find({ 'sync.upstream.urls': { $exists: 1 } }) + .sort({ $natural: -1 }) + .forEach(relay => { + db.relay.updateOne( + { _id: relay._id }, + { + $set: { + 'sync.upstream.urls': relay.sync.upstream.urls + .filter(u => !!u) + .map(url => (url.lcc ? lccUrl(url.lcc, url.round) : url.url)), + }, + }, + ); + }); diff --git a/bin/mongodb/relay-tour-info-migrate.js b/bin/mongodb/relay-tour-info-migrate.js new file mode 100644 index 0000000000000..dcff48b26fd6a --- /dev/null +++ b/bin/mongodb/relay-tour-info-migrate.js @@ -0,0 +1,24 @@ +db.relay_tour + .find({ + description: { $exists: true }, + 'info.dates': { $exists: false }, + 'info.format': { $exists: false }, + 'info.tc': { $exists: false }, + 'info.players': { $exists: false }, + }) + .forEach(function (tour) { + if (!tour.description.includes('|')) { + return; + } + const split = tour.description.split('|').map(x => x.trim()); + const info = {}; + const dates = split.shift(); + if (dates && /\d/.test(dates)) info.dates = dates; + const format = split.shift(); + if (format) info.format = format; + const tc = split.shift(); + if (tc) info.tc = tc.replace(/time control/i, '').trim(); + const players = split.shift(); + if (players) info.players = players; + db.relay_tour.updateOne({ _id: tour._id }, { $set: { info: info } }); + }); diff --git a/build.sbt b/build.sbt index bb26114807e4c..3c3d522bba750 100644 --- a/build.sbt +++ b/build.sbt @@ -393,7 +393,7 @@ lazy val practice = module("practice", lazy val playban = module("playban", Seq(memo), - Seq() + tests.bundle ) lazy val push = module("push", diff --git a/conf/routes b/conf/routes index 8cc00de17428e..a4071969f2967 100644 --- a/conf/routes +++ b/conf/routes @@ -100,6 +100,7 @@ GET /@/:username/:filterName controllers.User.games(username: UserStr, GET /@/:username controllers.User.show(username: UserStr) GET /player/myself controllers.User.myself GET /player/opponents controllers.User.opponents +GET /player/search/:term controllers.User.search(term: String) GET /player controllers.User.list GET /player/top/:nb/:perfKey controllers.User.topNb(nb: Int, perfKey: PerfKey) GET /api/player/top/:nb/:perfKey controllers.User.topNbApi(nb: Int, perfKey: PerfKey) @@ -262,7 +263,6 @@ GET /broadcast/all-private controllers.RelayTour.allPrivate(p GET /broadcast/:ts/$id<\w{8}> controllers.RelayTour.show(ts, id: RelayTourId) GET /api/broadcast/$id<\w{8}> controllers.RelayTour.apiShow(id: RelayTourId) GET /api/broadcast/$tourId<\w{8}>.pgn controllers.RelayTour.pgn(tourId: RelayTourId) -GET /broadcast/$tourId<\w{8}>/stats controllers.RelayTour.stats(tourId: RelayTourId) GET /broadcast/$tourId<\w{8}>/edit controllers.RelayTour.edit(tourId: RelayTourId) POST /broadcast/$tourId<\w{8}>/edit controllers.RelayTour.update(tourId: RelayTourId) POST /broadcast/$tourId<\w{8}>/delete controllers.RelayTour.delete(tourId: RelayTourId) @@ -278,11 +278,12 @@ GET /broadcast/round/$roundId<\w{8}>/edit controllers.RelayRound.edit(roundI POST /broadcast/round/$roundId<\w{8}>/edit controllers.RelayRound.update(roundId: RelayRoundId) POST /broadcast/round/$roundId<\w{8}>/reset controllers.RelayRound.reset(roundId: RelayRoundId) POST /broadcast/round/$roundId<\w{8}>/push controllers.RelayRound.push(roundId: RelayRoundId) +GET /broadcast/round/$roundId<\w{8}>/stats controllers.RelayRound.stats(roundId: RelayRoundId) POST /api/broadcast/round/$roundId<\w{8}>/push controllers.RelayRound.push(roundId: RelayRoundId) -GET /broadcast/:ts/:rs/$roundId<\w{8}>.pgn controllers.RelayRound.pgn(ts, rs, roundId: StudyId) +GET /broadcast/:ts/:rs/$roundId<\w{8}>.pgn controllers.RelayRound.pgn(ts, rs, roundId: RelayRoundId) GET /broadcast/$roundId<\w{8}>/teams controllers.RelayRound.teamsView(roundId: RelayRoundId) GET /broadcast/$tourId<\w{8}>/leaderboard controllers.RelayTour.leaderboardView(tourId: RelayTourId) -GET /api/broadcast/round/$roundId<\w{8}>.pgn controllers.RelayRound.apiPgn(roundId: StudyId) +GET /api/broadcast/round/$roundId<\w{8}>.pgn controllers.RelayRound.apiPgn(roundId: RelayRoundId) GET /api/stream/broadcast/round/$roundId<\w{8}>.pgn controllers.RelayRound.stream(roundId: RelayRoundId) GET /api/broadcast controllers.RelayTour.apiIndex GET /api/broadcast/top controllers.RelayTour.apiTop(page: Int ?= 1) @@ -598,15 +599,17 @@ GET /kaladin controllers.Irwin.kaladin # Forum GET /forum controllers.ForumCateg.index GET /forum/search controllers.ForumPost.search(text ?= "", page: Int ?= 1) -GET /forum/:categId controllers.ForumCateg.show(categId: ForumCategId, page: Int ?= 1) -GET /forum/:categId/form controllers.ForumTopic.form(categId: ForumCategId) -POST /forum/:categId/new controllers.ForumTopic.create(categId: ForumCategId) +GET /forum/:categId controllers.ForumCateg.show(categId: ForumCategId, page: Int ?= 1) +GET /forum/:categId/form controllers.ForumTopic.form(categId: ForumCategId) +POST /forum/:categId/new controllers.ForumTopic.create(categId: ForumCategId) +GET /forum/:categId/mod-feed controllers.ForumCateg.modFeed(categId: ForumCategId, page: Int ?= 1) GET /forum/participants/:topicId controllers.ForumTopic.participants(topicId: ForumTopicId) -GET /forum/:categId/:slug controllers.ForumTopic.show(categId: ForumCategId, slug, page: Int ?= 1) -POST /forum/:categId/:slug/close controllers.ForumTopic.close(categId: ForumCategId, slug) -POST /forum/:categId/:slug/sticky controllers.ForumTopic.sticky(categId: ForumCategId, slug) -POST /forum/:categId/:slug/new controllers.ForumPost.create(categId: ForumCategId, slug, page: Int ?= 1) +GET /forum/:categId/:slug controllers.ForumTopic.show(categId: ForumCategId, slug, page: Int ?= 1) +POST /forum/:categId/:slug/close controllers.ForumTopic.close(categId: ForumCategId, slug) +POST /forum/:categId/:slug/sticky controllers.ForumTopic.sticky(categId: ForumCategId, slug) +POST /forum/:categId/:slug/new controllers.ForumPost.create(categId: ForumCategId, slug, page: Int ?= 1) POST /forum/delete/:id controllers.ForumPost.delete(id: ForumPostId) +POST /forum/relocate/:id controllers.ForumPost.relocate(id: ForumPostId) POST /forum/:categId/react/:id/:reaction/:v controllers.ForumPost.react(categId: ForumCategId, id: ForumPostId, reaction, v: Boolean) POST /forum/post/:id controllers.ForumPost.edit(id: ForumPostId) GET /forum/redirect/post/:id controllers.ForumPost.redirect(id: ForumPostId) @@ -722,6 +725,7 @@ GET /api/bulk-pairing controllers.BulkPairing.list POST /api/bulk-pairing controllers.BulkPairing.create GET /api/bulk-pairing/:id controllers.BulkPairing.show(id) DELETE /api/bulk-pairing/:id controllers.BulkPairing.delete(id) +GET /api/bulk-pairing/:id/games controllers.BulkPairing.games(id) POST /api/bulk-pairing/:id/start-clocks controllers.BulkPairing.startClocks(id) GET /api/games/user/:username controllers.Game.apiExportByUser(username: UserStr) diff --git a/modules/analyse/src/main/ui/AnalyseI18n.scala b/modules/analyse/src/main/ui/AnalyseI18n.scala index ce5ab5661c83c..3a7c9b891e514 100644 --- a/modules/analyse/src/main/ui/AnalyseI18n.scala +++ b/modules/analyse/src/main/ui/AnalyseI18n.scala @@ -300,6 +300,8 @@ final class GameAnalyseI18n(helpers: Helpers, board: AnalyseI18n): site.promoteVariation, site.makeMainLine, site.deleteFromHere, + site.collapseVariations, + site.expandVariations, site.forceVariation, site.copyVariationPgn, // practice (also uses checkmate, draw) diff --git a/modules/api/src/main/Cli.scala b/modules/api/src/main/Cli.scala index da551708e9062..ba31e32da96ff 100644 --- a/modules/api/src/main/Cli.scala +++ b/modules/api/src/main/Cli.scala @@ -5,8 +5,6 @@ import lila.web.AnnounceApi final private[api] class Cli( security: lila.security.Env, - teamSearch: lila.teamSearch.Env, - forumSearch: lila.forumSearch.Env, tournament: lila.tournament.Env, fishnet: lila.fishnet.Env, study: lila.study.Env, @@ -55,7 +53,6 @@ final private[api] class Cli( private def processors = security.cli.process - .orElse(teamSearch.cli.process) .orElse(tournament.cli.process) .orElse(fishnet.cli.process) .orElse(study.cli.process) diff --git a/modules/api/src/main/Env.scala b/modules/api/src/main/Env.scala index 633b653767d38..5c0e09c731ced 100644 --- a/modules/api/src/main/Env.scala +++ b/modules/api/src/main/Env.scala @@ -54,6 +54,7 @@ final class Env( notifyEnv: lila.notify.Env, appealApi: lila.appeal.AppealApi, shutupEnv: lila.shutup.Env, + titleEnv: lila.title.Env, modLogApi: lila.mod.ModlogApi, activityWriteApi: lila.activity.ActivityWriteApi, ublogApi: lila.ublog.UblogApi, diff --git a/modules/api/src/main/PersonalDataExport.scala b/modules/api/src/main/PersonalDataExport.scala index 5a1a3615cd231..12ac1a8e829fa 100644 --- a/modules/api/src/main/PersonalDataExport.scala +++ b/modules/api/src/main/PersonalDataExport.scala @@ -25,6 +25,7 @@ final class PersonalDataExport( shutupEnv: lila.shutup.Env, modLogApi: lila.mod.ModlogApi, reportEnv: lila.report.Env, + titleEnv: lila.title.Env, picfitUrl: lila.memo.PicfitUrl )(using Executor, Materializer): @@ -226,10 +227,27 @@ final class PersonalDataExport( val timeoutMsg = m.details.so(_.split(":").drop(1).mkString(":").trim()) s"${textDate(m.date)}\n${timeoutMsg}$bigSep" + val titleRequests = Source.futureSource: + titleEnv.api + .allOf(user) + .map: reqs => + Source: + List(textTitle("Title request")) ++ reqs.map: req => + import req.data.* + s"""Title: $title + | Real name: $realName + | FIDE ID: ${fideId | "-"} + | Federation URL: ${federationUrl | "-"} + | Public: $public + | Coach: ${req.data.coach} + | Comment: ${comment | "-"} + | $bigSep""".stripMargin + val outro = Source(List(textTitle("End of data export."))) List[Source[String, ?]]( intro, + titleRequests, connections, followedUsers, streamer, diff --git a/modules/api/src/main/TextLpvExpand.scala b/modules/api/src/main/TextLpvExpand.scala index 7b1f88e3a1593..1bead818501e6 100644 --- a/modules/api/src/main/TextLpvExpand.scala +++ b/modules/api/src/main/TextLpvExpand.scala @@ -54,6 +54,7 @@ final class TextLpvExpand( def allPgnsFromText(text: String): Fu[Map[String, LpvEmbed]] = regex.blogPgnCandidatesRe .findAllMatchIn(text) + .take(20) .map(_.group(1)) .map: case regex.gamePgnRe(url, id) => getPgn(GameId(id)).map(id -> _) diff --git a/modules/api/src/main/UserApi.scala b/modules/api/src/main/UserApi.scala index 25114a9fce2b1..0b1099aac33e2 100644 --- a/modules/api/src/main/UserApi.scala +++ b/modules/api/src/main/UserApi.scala @@ -10,6 +10,7 @@ import lila.core.perm.Granter import lila.user.Trophy import lila.rating.PerfType import lila.core.perf.UserWithPerfs +import lila.core.LightUser final class UserApi( jsonView: lila.user.JsonView, @@ -27,27 +28,33 @@ final class UserApi( trophyApi: lila.user.TrophyApi, shieldApi: lila.tournament.TournamentShieldApi, revolutionApi: lila.tournament.RevolutionApi, + challengeGranter: lila.challenge.ChallengeGranter, net: NetConfig )(using Executor, lila.core.i18n.Translator): - def one(u: UserWithPerfs, joinedAt: Option[Instant] = None): JsObject = { - addStreaming(jsonView.full(u.user, u.perfs.some, withProfile = true), u.id) ++ - Json.obj("url" -> makeUrl(s"@/${u.username}")) // for app BC + def one(u: UserWithPerfs | LightUser, joinedAt: Option[Instant] = None): JsObject = { + val (light, userJson) = u match + case u: UserWithPerfs => (u.user.light, jsonView.full(u.user, u.perfs.some, withProfile = false)) + case u: LightUser => (u, Json.toJsObject(u)) + addStreaming(userJson, light.id) ++ + Json.obj("url" -> makeUrl(s"@/${light.name}")) // for app BC }.add("joinedTeamAt", joinedAt) def extended( username: UserStr, withFollows: Boolean, - withTrophies: Boolean + withTrophies: Boolean, + withCanChallenge: Boolean )(using Option[Me], Lang): Fu[Option[JsObject]] = userApi.withPerfs(username).flatMapz { - extended(_, withFollows, withTrophies).dmap(some) + extended(_, withFollows, withTrophies, withCanChallenge).dmap(some) } def extended( u: User | UserWithPerfs, withFollows: Boolean, withTrophies: Boolean, + withCanChallenge: Boolean, forWiki: Boolean = false )(using as: Option[Me], lang: Lang): Fu[JsObject] = u.match @@ -69,6 +76,7 @@ final class UserApi( gameCache.nbImportedBy(u.id), (withTrophies && !u.lame).soFu(getTrophiesAndAwards(u.user)), streamerApi.listed(u.user), + withCanChallenge.so(challengeGranter.mayChallenge(u.user).dmap(some)), forWiki.soFu(userRepo.email(u.id)) ).mapN: ( @@ -83,6 +91,7 @@ final class UserApi( nbImported, trophiesAndAwards, streamer, + canChallenge, email ) => jsonView.full(u.user, u.perfs.some, withProfile = true) ++ { @@ -112,6 +121,7 @@ final class UserApi( .add("nbFollowing", following) .add("nbFollowers", withFollows.option(0)) .add("trophies", trophiesAndAwards.map(trophiesJson)) + .add("canChallenge", canChallenge) .add( "streamer", streamer.map: s => diff --git a/modules/challenge/src/main/BSONHandlers.scala b/modules/challenge/src/main/BSONHandlers.scala index 28109b9c1d9bd..6a01127ee7c7c 100644 --- a/modules/challenge/src/main/BSONHandlers.scala +++ b/modules/challenge/src/main/BSONHandlers.scala @@ -13,18 +13,14 @@ private object BSONHandlers: import Challenge.* import lila.game.BSONHandlers.given - given BSONHandler[ColorChoice] = BSONIntegerHandler.as[ColorChoice]( - { - case 1 => ColorChoice.White - case 2 => ColorChoice.Black - case _ => ColorChoice.Random - }, - { - case ColorChoice.White => 1 - case ColorChoice.Black => 2 - case ColorChoice.Random => 0 - } - ) + given BSONHandler[ColorChoice] = + val map = Map( + 0 -> ColorChoice.Random, + 1 -> ColorChoice.White, + 2 -> ColorChoice.Black + ) + valueMapHandler[Int, ColorChoice](map)(i => map.find(_._2 == i).so(_._1)) + given BSON[TimeControl] with import chess.Clock def reads(r: Reader) = diff --git a/modules/challenge/src/main/ChallengeGranter.scala b/modules/challenge/src/main/ChallengeGranter.scala index cae67882d0350..cce3747dbc1c1 100644 --- a/modules/challenge/src/main/ChallengeGranter.scala +++ b/modules/challenge/src/main/ChallengeGranter.scala @@ -42,38 +42,42 @@ final class ChallengeGranter( val ratingThreshold = 300 - def isDenied(dest: User, perfKey: PerfKey)(using + def mayChallenge(dest: User)(using Executor)(using me: Option[Me]): Fu[Boolean] = + isDenied(dest, None).map(_.isEmpty) + + // perfkey is None when we're not yet trying to challenge + def isDenied(dest: User, perfKey: Option[PerfKey])(using Executor )(using me: Option[Me]): Fu[Option[ChallengeDenied]] = me - .fold[Fu[Option[ChallengeDenied.Reason]]] { - prefApi.getChallenge(dest.id).map { - case lila.core.pref.Challenge.ALWAYS => none - case _ => YouAreAnon.some - } - } { from => - type Res = Option[ChallengeDenied.Reason] - given Conversion[Res, Fu[Res]] = fuccess - relationApi.fetchRelation(dest.id, from.userId).zip(prefApi.getChallenge(dest.id)).flatMap { - case (Some(Block), _) => YouAreBlocked.some - case (_, lila.core.pref.Challenge.NEVER) => TheyDontAcceptChallenges.some - case (Some(Follow), _) => none // always accept from followed - case (_, _) if from.marks.engine && !dest.marks.engine => YouAreBlocked.some - case (_, lila.core.pref.Challenge.FRIEND) => FriendsOnly.some - case (_, lila.core.pref.Challenge.RATING) => - userApi - .perfsOf(from.value -> dest, primary = false) - .map: (fromPerfs, destPerfs) => - if fromPerfs(perfKey).provisional || destPerfs(perfKey).provisional - then RatingIsProvisional(perfKey).some - else - val diff = - math.abs(fromPerfs(perfKey).intRating.value - destPerfs(perfKey).intRating.value) - (diff > ratingThreshold).option(RatingOutsideRange(perfKey)) - case (_, lila.core.pref.Challenge.REGISTERED) => none - case _ if from == dest => SelfChallenge.some - case _ => none - } - } + .match + case None => + prefApi.getChallenge(dest.id).map { + case lila.core.pref.Challenge.ALWAYS => none + case _ => YouAreAnon.some + } + case Some(from) => + type Res = Option[ChallengeDenied.Reason] + given Conversion[Res, Fu[Res]] = fuccess + relationApi.fetchRelation(dest.id, from.userId).zip(prefApi.getChallenge(dest.id)).flatMap { + case (Some(Block), _) => YouAreBlocked.some + case (_, lila.core.pref.Challenge.NEVER) => TheyDontAcceptChallenges.some + case (Some(Follow), _) => none // always accept from followed + case (_, _) if from.marks.engine && !dest.marks.engine => YouAreBlocked.some + case (_, lila.core.pref.Challenge.FRIEND) => FriendsOnly.some + case (_, lila.core.pref.Challenge.RATING) => + perfKey.so: pk => + userApi + .perfsOf(from.value -> dest, primary = false) + .map: (fromPerfs, destPerfs) => + if fromPerfs(pk).provisional || destPerfs(pk).provisional + then RatingIsProvisional(pk).some + else + val diff = math.abs(fromPerfs(pk).intRating.value - destPerfs(pk).intRating.value) + (diff > ratingThreshold).option(RatingOutsideRange(pk)) + case (_, lila.core.pref.Challenge.REGISTERED) => none + case _ if from == dest => SelfChallenge.some + case _ => none + } .map: case None if dest.isBot && perfKey == PerfKey.ultraBullet => BotUltraBullet.some case res => res diff --git a/modules/challenge/src/main/ui/ChallengeUi.scala b/modules/challenge/src/main/ui/ChallengeUi.scala index f49a310b9633f..604f877ba12f8 100644 --- a/modules/challenge/src/main/ui/ChallengeUi.scala +++ b/modules/challenge/src/main/ui/ChallengeUi.scala @@ -8,6 +8,7 @@ import ScalatagsTemplate.{ *, given } import lila.core.LightUser import lila.challenge.Challenge.Status import lila.core.user.WithPerf +import lila.core.game.GameRule final class ChallengeUi(helpers: Helpers): import helpers.{ *, given } @@ -50,29 +51,50 @@ final class ChallengeUi(helpers: Helpers): s"$speed$variant ${c.mode.name} Chess • $players" private def details(c: Challenge, requestedColor: Option[Color])(using ctx: Context) = - div(cls := "details")( - div( - cls := "variant", - dataIcon := (if c.initialFen.isDefined then Icon.Feather else c.perfType.icon) - )( + div(cls := "details-wrapper")( + div(cls := "content")( div( - variantLink(c.variant, c.perfType, c.initialFen), - br, - span(cls := "clock"): - c.daysPerTurn - .fold(shortClockName(c.clock.map(_.config))): days => - if days.value == 1 then trans.site.oneDay() - else trans.site.nbDays.pluralSame(days.value) + cls := "variant", + dataIcon := (if c.initialFen.isDefined then Icon.Feather else c.perfType.icon) + )( + div( + variantLink(c.variant, c.perfType, c.initialFen), + br, + span(cls := "clock"): + c.daysPerTurn + .fold(shortClockName(c.clock.map(_.config))): days => + if days.value == 1 then trans.site.oneDay() + else trans.site.nbDays.pluralSame(days.value) + ) + ), + div(cls := "mode")( + c.open.fold(c.colorChoice.some)(_.colorFor(requestedColor)).map { colorChoice => + frag(colorChoice.trans(), br) + }, + modeName(c.mode) ) ), - div(cls := "mode")( - c.open.fold(c.colorChoice.some)(_.colorFor(requestedColor)).map { colorChoice => - frag(colorChoice.trans(), br) - }, - modeName(c.mode) + div(cls := "rules")( + h2("Custom rules:"), + div(fragList(c.rules.toList.map(showRule), "/")) ) ) + private def showRule(r: GameRule) = + val (text, flair) = getRuleStyle(r); + div(cls := "challenge-rule")( + iconFlair(flair), + text + ) + + private def getRuleStyle(r: GameRule): (String, Flair) = + r match + case GameRule.noAbort => ("No abort", Flair("symbols.cross-mark")); + case GameRule.noRematch => ("No rematch", Flair("symbols.recycling-symbol")); + case GameRule.noGiveTime => ("No giving of time", Flair("objects.alarm-clock")); + case GameRule.noClaimWin => ("No claiming of win", Flair("objects.hourglass-done")); + case GameRule.noEarlyDraw => ("No early draw", Flair("people.handshake-light-skin-tone")); + def mine( c: Challenge, json: JsObject, diff --git a/modules/coach/src/main/ui/CoachEditUi.scala b/modules/coach/src/main/ui/CoachEditUi.scala index ec30e74edffdd..4a1e69e9b0894 100644 --- a/modules/coach/src/main/ui/CoachEditUi.scala +++ b/modules/coach/src/main/ui/CoachEditUi.scala @@ -47,9 +47,11 @@ final class CoachEditUi(helpers: Helpers, ui: CoachUi): h3("TODO list before publishing your coach profile"), ul ), - div(cls := "picture_wrap")( - ui.thumbnail(c, 250)(attr("draggable") := "true", cls := "drop-target"), - div(label("Drag file or"), " ", form3.file.selectImage()) + form3.fieldset("Picture", toggle = true.some)( + div(cls := "form-group coach-edit-picture")( + ui.thumbnail(c, 250)(attr("draggable") := "true", cls := "drop-target"), + div(label("Drag file or"), " ", form3.file.selectImage()) + ) ) ) ), diff --git a/modules/common/src/main/Chronometer.scala b/modules/common/src/main/Chronometer.scala index 0c8b4d37fbccf..6af6ec9ddae42 100644 --- a/modules/common/src/main/Chronometer.scala +++ b/modules/common/src/main/Chronometer.scala @@ -1,7 +1,5 @@ package lila.common -import scala.util.Try - object Chronometer: object futureExtension: @@ -25,7 +23,7 @@ object Chronometer: def chronometerTry = Chronometer.lapTry(fua) def mon(path: lila.mon.TimerPath): Fu[A] = chronometer.mon(path).result - def monTry(path: Try[A] => lila.mon.TimerPath): Fu[A] = + def monTry(path: scala.util.Try[A] => lila.mon.TimerPath): Fu[A] = chronometerTry.mon(r => path(r)(lila.mon)).result def monSuccess(path: lila.mon.type => Boolean => kamon.metric.Timer): Fu[A] = chronometerTry @@ -72,7 +70,7 @@ object Chronometer: def showDuration: String = if millis >= 1 then s"$millis ms" else s"$micros micros" - case class LapTry[A](result: Try[A], nanos: Long): + case class LapTry[A](result: scala.util.Try[A], nanos: Long): def millis = (nanos / 1000000).toInt case class FuLap[A](lap: Fu[Lap[A]]) extends AnyVal: @@ -105,7 +103,7 @@ object Chronometer: case class FuLapTry[A](lap: Fu[LapTry[A]]) extends AnyVal: - def mon(path: Try[A] => kamon.metric.Timer) = + def mon(path: scala.util.Try[A] => kamon.metric.Timer) = lap.dforeach: l => path(l.result).record(l.nanos) this diff --git a/modules/common/src/main/Form.scala b/modules/common/src/main/Form.scala index 3387ee77f3917..f97116eec9bd4 100644 --- a/modules/common/src/main/Form.scala +++ b/modules/common/src/main/Form.scala @@ -50,6 +50,9 @@ object Form: def numberInDouble(choices: Options[Double]) = of[Double].verifying(mustBeOneOf(choices.map(_._1)), hasKey(choices, _)) + def stringIn[A](choices: Seq[A])(key: A => String): Mapping[A] = + stringIn(choices.map(key).toSet).transform[A](str => choices.find(c => str == key(c)).get, key) + def id[Id](size: Int, fixed: Option[Id])(exists: Id => Fu[Boolean])(using sr: StringRuntime[Id], rs: SameRuntime[String, Id] @@ -78,6 +81,10 @@ object Form: val cleanText: Mapping[String] = of(cleanTextFormatter) val cleanTextWithSymbols: Mapping[String] = of(cleanTextFormatterWithSymbols) + val nonEmptyOrSpace = V.Constraint[String]: t => + if t.linesIterator.exists(_.stripLineEnd.exists(!_.isWhitespace)) then V.Valid + else V.Invalid(V.ValidationError("error.required")) + private def addLengthConstraints(m: Mapping[String], minLength: Int, maxLength: Int) = (minLength, maxLength) match case (min, Int.MaxValue) => m.verifying(Constraints.minLength(min)) @@ -87,9 +94,9 @@ object Form: def cleanText(minLength: Int = 0, maxLength: Int = Int.MaxValue): Mapping[String] = addLengthConstraints(cleanText, minLength, maxLength) - val cleanNonEmptyText: Mapping[String] = cleanText.verifying(Constraints.nonEmpty) + val cleanNonEmptyText: Mapping[String] = cleanText.verifying(nonEmptyOrSpace) def cleanNonEmptyText(minLength: Int = 0, maxLength: Int = Int.MaxValue): Mapping[String] = - cleanText(minLength, maxLength).verifying(Constraints.nonEmpty) + cleanText(minLength, maxLength).verifying(nonEmptyOrSpace) def cleanTextWithSymbols(minLength: Int = 0, maxLength: Int = Int.MaxValue): Mapping[String] = addLengthConstraints(cleanTextWithSymbols, minLength, maxLength) @@ -98,7 +105,7 @@ object Form: cleanTextWithSymbols(minLength, maxLength).verifying(noSymbolsConstraint) def cleanNoSymbolsAndNonEmptyText(minLength: Int = 0, maxLength: Int = Int.MaxValue): Mapping[String] = - cleanNoSymbolsText(minLength, maxLength).verifying(Constraints.nonEmpty) + cleanNoSymbolsText(minLength, maxLength).verifying(nonEmptyOrSpace) private val eventNameConstraint = Constraints.pattern( regex = """[\p{L}\p{N}-\s:.,;'°ª\+]+""".r, diff --git a/modules/common/src/main/HTTPRequest.scala b/modules/common/src/main/HTTPRequest.scala index 0dc2bdee8bded..078e45b38c215 100644 --- a/modules/common/src/main/HTTPRequest.scala +++ b/modules/common/src/main/HTTPRequest.scala @@ -64,7 +64,7 @@ object HTTPRequest: private val crawlerMatcher = UaMatcher: // spiders/crawlers - """Googlebot|AdsBot|Google-Read-Aloud|bingbot|BingPreview|facebookexternalhit|SemrushBot|AhrefsBot|PetalBot|Applebot|YandexBot|YandexAdNet|Twitterbot|Baiduspider|Amazonbot|Bytespider""" + + """Googlebot|AdsBot|Google-Read-Aloud|bingbot|BingPreview|facebookexternalhit|SemrushBot|AhrefsBot|PetalBot|Applebot|YandexBot|YandexAdNet|Twitterbot|Baiduspider|Amazonbot|Bytespider|yacybot""" + // http libs """|HeadlessChrome|okhttp|axios|wget|curl|python-requests|aiohttp|commons-httpclient|python-urllib|python-httpx|Nessus""" diff --git a/modules/common/src/main/Json.scala b/modules/common/src/main/Json.scala index 56b24cdfc8026..a995bb66e7883 100644 --- a/modules/common/src/main/Json.scala +++ b/modules/common/src/main/Json.scala @@ -1,8 +1,8 @@ package lila.common import play.api.libs.json.{ Json as PlayJson, * } - import scala.util.NotGiven +import io.mola.galimatias.URL object Json: @@ -18,6 +18,8 @@ object Json: given Writes[PerfKey] = pk => JsString(PerfKey.value(pk)) + given Writes[URL] = url => JsString(url.toString) + given [A](using Show[A]): KeyWrites[A] with def writeKey(key: A) = key.show diff --git a/modules/common/src/main/mon.scala b/modules/common/src/main/mon.scala index 6fcfe9420ec23..6c261978b7530 100644 --- a/modules/common/src/main/mon.scala +++ b/modules/common/src/main/mon.scala @@ -157,6 +157,9 @@ object mon: object correspondenceEmail: val emails = histogram("round.correspondenceEmail.emails").withoutTags() val time = future("round.correspondenceEmail.time") + object farming: + val bot = counter("round.farming.bot").withoutTags() + val provisional = counter("round.farming.provisional").withoutTags() object playban: def outcome(out: String) = counter("playban.outcome").withTag("outcome", out) object ban: @@ -277,16 +280,20 @@ object mon: def zoneSegment(name: String) = future("mod.zone.segment", name) object relay: private def by(official: Boolean) = if official then "official" else "user" - private def relay(official: Boolean, slug: String) = - tags("by" -> by(official), "slug" -> slug) - def ongoing(official: Boolean) = gauge("relay.ongoing").withTag("by", by(official)) - def games(official: Boolean, slug: String) = gauge("relay.games").withTags(relay(official, slug)) - def moves(official: Boolean, slug: String) = counter("relay.moves").withTags(relay(official, slug)) - def fetchTime(official: Boolean, slug: String) = timer("relay.fetch.time").withTags(relay(official, slug)) - def syncTime(official: Boolean, slug: String) = timer("relay.sync.time").withTags(relay(official, slug)) - def tourCrowd(tourId: RelayTourId) = gauge("relay.tour.crowd").withTag("tour", tourId.value) + private def relay(official: Boolean, id: RelayTourId, slug: String) = + tags("by" -> by(official), "slug" -> s"$slug/$id") + def ongoing(official: Boolean) = gauge("relay.ongoing").withTag("by", by(official)) + def games(official: Boolean, id: RelayTourId, slug: String) = + gauge("relay.games").withTags(relay(official, id, slug)) + def moves(official: Boolean, id: RelayTourId, slug: String) = + counter("relay.moves").withTags(relay(official, id, slug)) + def fetchTime(official: Boolean, id: RelayTourId, slug: String) = + timer("relay.fetch.time").withTags(relay(official, id, slug)) + def syncTime(official: Boolean, id: RelayTourId, slug: String) = + timer("relay.sync.time").withTags(relay(official, id, slug)) def httpGet(host: String, proxy: Option[String]) = future("relay.http.get", tags("host" -> host, "proxy" -> proxy.getOrElse("none"))) + val dedup = counter("relay.fetch.dedup").withoutTags() object bot: def moves(username: String) = counter("bot.moves").withTag("name", username) @@ -328,6 +335,9 @@ object mon: object verifyMailApi: def fetch(success: Boolean, ok: Boolean) = timer("verifyMail.fetch").withTags(tags("success" -> successTag(success), "ok" -> ok)) + object mailcheckApi: + def fetch(success: Boolean, ok: Boolean) = + timer("mailcheck.fetch").withTags(tags("success" -> successTag(success), "ok" -> ok)) def usersAlikeTime(field: String) = timer("security.usersAlike.time").withTag("field", field) def usersAlikeFound(field: String) = histogram("security.usersAlike.found").withTag("field", field) object hCaptcha: diff --git a/modules/core/src/main/game/Game.scala b/modules/core/src/main/game/Game.scala index 749eef64a3c59..b120971e5635d 100644 --- a/modules/core/src/main/game/Game.scala +++ b/modules/core/src/main/game/Game.scala @@ -73,7 +73,7 @@ case class Game( def turnOf(c: Color): Boolean = c == turnColor def turnOf(u: User): Boolean = player(u).exists(turnOf) - def playedTurns = ply - startedAtPly + def playedTurns: Ply = ply - startedAtPly def flagged = (status == Status.Outoftime).option(turnColor) diff --git a/modules/core/src/main/team.scala b/modules/core/src/main/team.scala index f5dc3d3bb5984..1b2dc5536440f 100644 --- a/modules/core/src/main/team.scala +++ b/modules/core/src/main/team.scala @@ -56,8 +56,6 @@ case class TeamData( ) case class TeamCreate(team: TeamData) case class TeamUpdate(team: TeamData, byMod: Boolean)(using val me: MyId) -case class TeamDelete(id: TeamId) -case class TeamDisable(id: TeamId) case class JoinTeam(id: TeamId, userId: UserId) case class IsLeader(id: TeamId, userId: UserId, promise: Promise[Boolean]) case class IsLeaderOf(leaderId: UserId, memberId: UserId, promise: Promise[Boolean]) diff --git a/modules/core/src/main/user.scala b/modules/core/src/main/user.scala index 74f4cf4e69603..4566fcc78a67e 100644 --- a/modules/core/src/main/user.scala +++ b/modules/core/src/main/user.scala @@ -111,8 +111,7 @@ object user: @Key("country") flag: Option[String] = None, location: Option[String] = None, bio: Option[String] = None, - firstName: Option[String] = None, - lastName: Option[String] = None, + realName: Option[String] = None, fideRating: Option[Int] = None, uscfRating: Option[Int] = None, ecfRating: Option[Int] = None, @@ -121,10 +120,7 @@ object user: dsbRating: Option[Int] = None, links: Option[String] = None ): - def nonEmptyRealName = - List(ne(firstName), ne(lastName)).flatten match - case Nil => none - case names => (names.mkString(" ")).some + def nonEmptyRealName = ne(realName) def nonEmptyLocation = ne(location) @@ -133,9 +129,10 @@ object user: def isEmpty = completionPercent == 0 def completionPercent: Int = - 100 * List(flag, bio, firstName, lastName).count(_.isDefined) / 4 + 100 * List(flag, bio, realName).count(_.isDefined) / 4 private def ne(str: Option[String]) = str.filter(_.nonEmpty) + end Profile object Profile: diff --git a/modules/coreI18n/src/main/key.scala b/modules/coreI18n/src/main/key.scala index 00da5602b6fc9..6f04bc38d9e2d 100644 --- a/modules/coreI18n/src/main/key.scala +++ b/modules/coreI18n/src/main/key.scala @@ -355,9 +355,6 @@ object I18nKey: val `safeTournamentName`: I18nKey = "safeTournamentName" val `inappropriateNameWarning`: I18nKey = "inappropriateNameWarning" val `emptyTournamentName`: I18nKey = "emptyTournamentName" - val `recommendNotTouching`: I18nKey = "recommendNotTouching" - val `fewerPlayers`: I18nKey = "fewerPlayers" - val `showAdvancedSettings`: I18nKey = "showAdvancedSettings" val `makePrivateTournament`: I18nKey = "makePrivateTournament" val `join`: I18nKey = "join" val `withdraw`: I18nKey = "withdraw" @@ -397,8 +394,7 @@ object I18nKey: val `ifNoneLeaveEmpty`: I18nKey = "ifNoneLeaveEmpty" val `profile`: I18nKey = "profile" val `editProfile`: I18nKey = "editProfile" - val `firstName`: I18nKey = "firstName" - val `lastName`: I18nKey = "lastName" + val `realName`: I18nKey = "realName" val `setFlair`: I18nKey = "setFlair" val `flair`: I18nKey = "flair" val `youCanHideFlair`: I18nKey = "youCanHideFlair" @@ -441,7 +437,6 @@ object I18nKey: val `reason`: I18nKey = "reason" val `whatIsIheMatter`: I18nKey = "whatIsIheMatter" val `cheat`: I18nKey = "cheat" - val `insult`: I18nKey = "insult" val `troll`: I18nKey = "troll" val `ratingManipulation`: I18nKey = "ratingManipulation" val `other`: I18nKey = "other" @@ -478,6 +473,7 @@ object I18nKey: val `slow`: I18nKey = "slow" val `insideTheBoard`: I18nKey = "insideTheBoard" val `outsideTheBoard`: I18nKey = "outsideTheBoard" + val `allSquaresOfTheBoard`: I18nKey = "allSquaresOfTheBoard" val `onSlowGames`: I18nKey = "onSlowGames" val `always`: I18nKey = "always" val `never`: I18nKey = "never" @@ -1671,8 +1667,6 @@ object I18nKey: val `startDate`: I18nKey = "broadcast:startDate" val `startDateHelp`: I18nKey = "broadcast:startDateHelp" val `credits`: I18nKey = "broadcast:credits" - val `broadcastUrl`: I18nKey = "broadcast:broadcastUrl" - val `currentRoundUrl`: I18nKey = "broadcast:currentRoundUrl" val `currentGameUrl`: I18nKey = "broadcast:currentGameUrl" val `downloadAllRounds`: I18nKey = "broadcast:downloadAllRounds" val `resetRound`: I18nKey = "broadcast:resetRound" @@ -1687,6 +1681,15 @@ object I18nKey: val `replacePlayerTags`: I18nKey = "broadcast:replacePlayerTags" val `periodInSeconds`: I18nKey = "broadcast:periodInSeconds" val `periodInSecondsHelp`: I18nKey = "broadcast:periodInSecondsHelp" + val `fideFederations`: I18nKey = "broadcast:fideFederations" + val `top10Rating`: I18nKey = "broadcast:top10Rating" + val `fidePlayers`: I18nKey = "broadcast:fidePlayers" + val `fidePlayerNotFound`: I18nKey = "broadcast:fidePlayerNotFound" + val `fideProfile`: I18nKey = "broadcast:fideProfile" + val `federation`: I18nKey = "broadcast:federation" + val `ageThisYear`: I18nKey = "broadcast:ageThisYear" + val `unrated`: I18nKey = "broadcast:unrated" + val `recentTournaments`: I18nKey = "broadcast:recentTournaments" val `nbBroadcasts`: I18nKey = "broadcast:nbBroadcasts" object streamer: diff --git a/modules/fide/src/main/FidePlayer.scala b/modules/fide/src/main/FidePlayer.scala index fb678935364ff..6e362bbc7481b 100644 --- a/modules/fide/src/main/FidePlayer.scala +++ b/modules/fide/src/main/FidePlayer.scala @@ -1,5 +1,6 @@ package lila.fide +import scala.util.chaining.* import chess.{ FideId, PlayerName, PlayerTitle } import reactivemongo.api.bson.Macros.Annotations.Key @@ -52,10 +53,15 @@ object FidePlayer: .toList .map(_.trim) .filter(_.nonEmpty) + .pipe(trimTitle) .distinct .sorted .mkString(" ") + private def trimTitle(name: List[String]): List[String] = name match + case title :: rest if PlayerTitle.get(title).isDefined => rest + case _ => name + val slugify: PlayerName => String = val splitAccentRegex = "[\u0300-\u036f]".r val multiSpaceRegex = """\s+""".r diff --git a/modules/fide/src/main/ui/FideUi.scala b/modules/fide/src/main/ui/FideUi.scala index f56dd7d6c271f..43089bd9c4e55 100644 --- a/modules/fide/src/main/ui/FideUi.scala +++ b/modules/fide/src/main/ui/FideUi.scala @@ -33,15 +33,15 @@ final class FideUi(helpers: Helpers)(menu: String => Context ?=> Frag): td(if stats.top10Rating > 0 then stats.top10Rating else "-") page("FIDE federations", "federations")( cls := "fide-federations", - boxTop(h1("FIDE federations")), + boxTop(h1(trans.broadcast.fideFederations())), table(cls := "slist slist-pad")( thead: tr( - th("Name"), - th("Players"), - th("Classic"), - th("Rapid"), - th("Blitz") + th(trans.site.name()), + th(trans.site.players()), + th(trans.site.classical()), + th(trans.site.rapid()), + th(trans.site.blitz()) ) , tbody(cls := "infinite-scroll")( @@ -72,9 +72,9 @@ final class FideUi(helpers: Helpers)(menu: String => Context ?=> Frag): card( name(), frag( - p("Rank", strong(stats.get.rank)), - p("Top 10 rating", strong(stats.get.top10Rating)), - p("Players", strong(stats.get.nbPlayers.localize)) + p(trans.site.rank(), strong(stats.get.rank)), + p(trans.broadcast.top10Rating(), strong(stats.get.top10Rating)), + p(trans.site.players(), strong(stats.get.nbPlayers.localize)) ) ) ), @@ -104,7 +104,7 @@ final class FideUi(helpers: Helpers)(menu: String => Context ?=> Frag): page("FIDE players", "players")( cls := "fide-players", boxTop( - h1("FIDE players"), + h1(trans.broadcast.fidePlayers()), div(cls := "box__top__actions"): searchForm(query) ), @@ -115,7 +115,7 @@ final class FideUi(helpers: Helpers)(menu: String => Context ?=> Frag): page("FIDE player not found", "players")( cls := "fide-players", boxTop( - h1("FIDE player not found"), + h1(trans.broadcast.fidePlayerNotFound()), div(cls := "box__top__actions"): searchForm("") ), @@ -158,12 +158,12 @@ final class FideUi(helpers: Helpers)(menu: String => Context ?=> Frag): table(cls := "slist slist-pad")( thead: tr( - th(title), + th(trans.site.name()), withFlag.option(th(iconTag(Icon.FlagOutline))), - th("Classic"), - th("Rapid"), - th("Blitz"), - th("Age this year") + th(trans.site.classical()), + th(trans.site.rapid()), + th(trans.site.blitz()), + th(trans.broadcast.ageThisYear()) ) , tbody(cls := "infinite-scroll")( @@ -194,7 +194,7 @@ final class FideUi(helpers: Helpers)(menu: String => Context ?=> Frag): div(cls := "fide-cards fide-player__cards")( player.fed.map: fed => card( - "Federation", + trans.broadcast.federation(), if fed == Federation.idNone then "None" else a(cls := "fide-player__federation", href := routes.Fide.federation(Federation.idToSlug(fed)))( @@ -203,16 +203,16 @@ final class FideUi(helpers: Helpers)(menu: String => Context ?=> Frag): ) ), card( - "FIDE profile", + trans.broadcast.fideProfile(), a(href := s"https://ratings.fide.com/profile/${player.id}")(player.id) ), card( - "Age this year", + trans.broadcast.ageThisYear(), player.age ), tcTrans.map: (tc, name) => - card(name(), player.ratingOf(tc).fold("Unrated")(_.toString)), + card(name(), player.ratingOf(tc).fold(trans.broadcast.unrated())(_.toString)), ), tours.map: tours => - div(cls := "fide-player__tours")(h2("Recent tournaments"), tours) + div(cls := "fide-player__tours")(h2(trans.broadcast.recentTournaments()), tours) ) diff --git a/modules/forum/src/main/ForumForm.scala b/modules/forum/src/main/ForumForm.scala index e6e014dd0fe8e..3b35386e3977d 100644 --- a/modules/forum/src/main/ForumForm.scala +++ b/modules/forum/src/main/ForumForm.scala @@ -3,7 +3,7 @@ package lila.forum import play.api.data.* import play.api.data.Forms.* -import lila.common.Form.cleanText +import lila.common.Form.{ cleanText, into } import lila.common.Form.given final private[forum] class ForumForm( @@ -42,14 +42,17 @@ final private[forum] class ForumForm( val deleteWithReason = Form: single("reason" -> optional(nonEmptyText)) + val relocateTo = Form: + single("categ" -> nonEmptyText.into[ForumCategId]) + private def userTextMapping(inOwnTeam: Boolean, previousText: Option[String] = None)(using me: Me) = - cleanText(minLength = 3) + cleanText(minLength = 3, 20_000) .verifying( "You have reached the daily maximum for links in forum posts.", t => inOwnTeam || promotion.test(me, t, previousText) ) - val diagnostic = Form(single("text" -> nonEmptyText(maxLength = 100000))) + val diagnostic = Form(single("text" -> nonEmptyText(maxLength = 100_000))) object ForumForm: diff --git a/modules/forum/src/main/ForumPaginator.scala b/modules/forum/src/main/ForumPaginator.scala index 4165fb7666ae3..c960b73357db2 100644 --- a/modules/forum/src/main/ForumPaginator.scala +++ b/modules/forum/src/main/ForumPaginator.scala @@ -15,6 +15,18 @@ final class ForumPaginator( import BSONHandlers.given + def recent(categ: ForumCateg, page: Int): Fu[Paginator[ForumPost]] = + Paginator( + Adapter[ForumPost]( + collection = postRepo.coll, + selector = postRepo.selectCateg(categ.id), + projection = none, + sort = $sort.createdDesc + ).withLotsOfResults, + currentPage = page, + maxPerPage = MaxPerPage(30) + ) + def topicPosts(topic: ForumTopic, page: Int)(using me: Option[Me])(using netDomain: NetDomain ): Fu[Paginator[ForumPost.WithFrag]] = @@ -23,7 +35,7 @@ final class ForumPaginator( collection = postRepo.coll, selector = postRepo.forUser(me).selectTopic(topic.id), projection = none, - sort = postRepo.sortQuery + sort = $sort.createdAsc ).mapFutureList(textExpand.manyPosts), currentPage = page, maxPerPage = config.postMaxPerPage diff --git a/modules/forum/src/main/ForumPostApi.scala b/modules/forum/src/main/ForumPostApi.scala index 77dae8e497674..6a30f8a235d1a 100644 --- a/modules/forum/src/main/ForumPostApi.scala +++ b/modules/forum/src/main/ForumPostApi.scala @@ -132,11 +132,11 @@ final class ForumPostApi( react(categId, postId, reaction.key, false) } - def views(posts: List[ForumPost]): Fu[List[PostView]] = + def views(posts: Seq[ForumPost]): Fu[List[PostView]] = for topics <- topicRepo.coll.byIds[ForumTopic, ForumTopicId](posts.map(_.topicId).distinct) categs <- categRepo.coll.byIds[ForumCateg, ForumCategId](topics.map(_.categId).distinct) - yield posts.flatMap: post => + yield posts.toList.flatMap: post => for topic <- topics.find(_.id == post.topicId) categ <- categs.find(_.id == topic.categId) diff --git a/modules/forum/src/main/ForumPostRepo.scala b/modules/forum/src/main/ForumPostRepo.scala index e2e253e0d4a28..8317234e71ffc 100644 --- a/modules/forum/src/main/ForumPostRepo.scala +++ b/modules/forum/src/main/ForumPostRepo.scala @@ -102,8 +102,6 @@ final class ForumPostRepo(val coll: Coll, filter: Filter = Safe)(using Executor) ) ) - def sortQuery = $sort.createdAsc - def idsByTopicId(topicId: ForumTopicId): Fu[List[ForumPostId]] = coll.distinctEasy[ForumPostId, List]("_id", $doc("topicId" -> topicId), _.sec) diff --git a/modules/forum/src/main/ForumTopicApi.scala b/modules/forum/src/main/ForumTopicApi.scala index 70721331e6915..5fe7245bcecb0 100644 --- a/modules/forum/src/main/ForumTopicApi.scala +++ b/modules/forum/src/main/ForumTopicApi.scala @@ -39,11 +39,7 @@ final private class ForumTopicApi( .flatMapz: topic => show(categId, slug, topic.lastPage(config.postMaxPerPage)) - def show( - categId: ForumCategId, - slug: String, - page: Int - )(using + def show(categId: ForumCategId, slug: String, page: Int)(using NetDomain )(using me: Option[Me]): Fu[Option[(ForumCateg, ForumTopic, Paginator[ForumPost.WithFrag])]] = for @@ -107,9 +103,9 @@ final private class ForumTopicApi( case Some(dup) => fuccess(dup) case None => for - _ <- postRepo.coll.insert.one(post) _ <- topicRepo.coll.insert.one(topic.withPost(post)) _ <- categRepo.coll.update.one($id(categ.id), categ.withPost(topic, post)) + _ <- postRepo.coll.insert.one(post) yield promotion.save(me, post.text) val text = s"${topic.name} ${post.text}" @@ -155,9 +151,9 @@ final private class ForumTopicApi( } private def makeNewTopic(categ: ForumCateg, topic: ForumTopic, post: ForumPost) = for - _ <- postRepo.coll.insert.one(post) _ <- topicRepo.coll.insert.one(topic.withPost(post)) _ <- categRepo.coll.update.one($id(categ.id), categ.withPost(topic, post)) + _ <- postRepo.coll.insert.one(post) yield Bus.pub(CreatePost(post.mini)) def getSticky(categ: ForumCateg, forUser: Option[User]): Fu[List[TopicView]] = @@ -219,3 +215,9 @@ final private class ForumTopicApi( _ <- categRepo.coll.update .one($id(cat.id), cat.withoutTopic(topic, lastPostId, lastPostIdTroll)) yield () + + def relocate(topic: ForumTopicId, to: ForumCategId)(using Me): Funit = + for + _ <- topicRepo.coll.update.one($id(topic), $set("categId" -> to)) + _ <- postRepo.coll.update.one($doc("topicId" -> topic), $set("categId" -> to)) + yield () diff --git a/modules/forum/src/main/model.scala b/modules/forum/src/main/model.scala index 5f83951c19812..e97097d7cf0b8 100644 --- a/modules/forum/src/main/model.scala +++ b/modules/forum/src/main/model.scala @@ -31,11 +31,7 @@ case class TopicView( def name = topic.name def createdAt = topic.createdAt -case class PostView( - post: ForumPost, - topic: ForumTopic, - categ: ForumCateg -): +case class PostView(post: ForumPost, topic: ForumTopic, categ: ForumCateg): def show = post.showUserIdOrAuthor + " @ " + topic.name + " - " + post.text.take(80) def logFormatted = "%s / %s#%s / %s".format(categ.name, topic.name, post.number, post.text) diff --git a/modules/forum/src/main/ui/CategUi.scala b/modules/forum/src/main/ui/CategUi.scala index 0e7ab0d3095bb..2e5f8d65e37f3 100644 --- a/modules/forum/src/main/ui/CategUi.scala +++ b/modules/forum/src/main/ui/CategUi.scala @@ -40,15 +40,6 @@ final class CategUi(helpers: Helpers, bits: ForumBits): stickyPosts: List[TopicView] )(using Context) = - val newTopicButton = canWrite.option( - a( - href := routes.ForumTopic.form(categ.id), - cls := "button button-empty button-green text", - dataIcon := Icon.Pencil - ): - trans.site.createANewTopic() - ) - def showTopic(sticky: Boolean)(topic: TopicView) = tr(cls := List("sticky" -> sticky))( td(cls := "subject")( @@ -86,8 +77,23 @@ final class CategUi(helpers: Helpers, bits: ForumBits): ), categ.team.fold(frag(categ.name))(teamLink(_, true)) ), - div(cls := "box__top__actions"): - newTopicButton + div(cls := "box__top__actions")( + Granter + .opt(_.ModerateForum) + .option( + a( + href := routes.ForumCateg.modFeed(categ.id), + cls := "button button-empty text" + )("Mod feed") + ), + canWrite.option( + a( + href := routes.ForumTopic.form(categ.id), + cls := "button button-empty button-green text", + dataIcon := Icon.Pencil + )(trans.site.createANewTopic()) + ) + ) ), table(cls := "topics slist slist-pad")( thead( @@ -131,3 +137,38 @@ final class CategUi(helpers: Helpers, bits: ForumBits): td(a(href := postUrl)(momentFromNow(post.createdAt)), br, trans.site.by(bits.authorLink(post))) ) ) + + def modFeed( + categ: lila.forum.ForumCateg, + posts: Paginator[PostView] + )(using Context) = + val pager = paginationByQuery(routes.ForumCateg.modFeed(categ.id, 1), posts, showPost = true) + Page(categ.name) + .css("bits.forum") + .csp(_.withInlineIconFont) + .js(infiniteScrollEsmInit): + main(cls := "forum forum-mod-feed box")( + boxTop( + h1( + a( + href := routes.ForumCateg.show(categ.id), + dataIcon := Icon.LessThan, + cls := "text" + )(categ.name), + " mod feed" + ) + ), + table(cls := "slist slist-pad")( + thead(tr(th("User"), th("Topic"), th("Post"), th("Date"))), + tbody(cls := "infinite-scroll")( + posts.currentPageResults.map: p => + tr(cls := "paginated")( + td(userIdLink(p.post.userId)), + td(a(href := routes.ForumTopic.show(p.categ.id, p.topic.slug))(p.topic.name)), + td(shorten(p.post.text, 400)), + td(a(href := routes.ForumPost.redirect(p.post.id))(momentFromNow(p.post.createdAt))) + ), + pagerNextTable(posts, np => routes.ForumCateg.modFeed(categ.id, np).url) + ) + ) + ) diff --git a/modules/forum/src/main/ui/PostUi.scala b/modules/forum/src/main/ui/PostUi.scala index 907ba30f38667..2de81c2424e93 100644 --- a/modules/forum/src/main/ui/PostUi.scala +++ b/modules/forum/src/main/ui/PostUi.scala @@ -55,6 +55,14 @@ final class PostUi(helpers: Helpers, bits: ForumBits): ).some else frag( + (canModCateg && post.number == 1).option: + a( + cls := "mod mod-relocate button button-empty", + href := routes.ForumPost.relocate(post.id), + dataIcon := Icon.Forward, + title := "Relocate" + ) + , if canModCateg || topic.isUblogAuthor(me) then a( cls := "mod delete button button-empty", diff --git a/modules/forum/src/main/ui/TopicUi.scala b/modules/forum/src/main/ui/TopicUi.scala index 782832e9be1d5..fac7ecad7833c 100644 --- a/modules/forum/src/main/ui/TopicUi.scala +++ b/modules/forum/src/main/ui/TopicUi.scala @@ -167,7 +167,8 @@ final class TopicUi(helpers: Helpers, bits: ForumBits, postUi: PostUi)( ) ) ), - (canModCateg || ctx.me.exists(topic.isAuthor)).option(deleteModal) + (canModCateg || ctx.me.exists(topic.isAuthor)).option(deleteModal), + canModCateg.option(relocateModal(categ)) ) ), formWithCaptcha.map: (form, captcha) => @@ -253,3 +254,28 @@ final class TopicUi(helpers: Helpers, bits: ForumBits, postUi: PostUi)( ) ) ) + + private val relocateTo = List( + "general-chess-discussion" -> "General Chess Discussion", + "lichess-feedback" -> "Lichess Feedback", + "game-analysis" -> "Game Analysis", + "off-topic-discussion" -> "Off-Topic Discussion" + ) + + private def relocateModal(from: lila.forum.ForumCateg) = + div(cls := "forum-relocate-modal none")( + p("Move the entire thread to another forum"), + st.form(method := "post", cls := "form3")( + st.select( + name := "categ", + cls := "form-control" + )( + relocateTo.collect: + case (slug, name) if slug != from.id.value => st.option(value := slug)(name) + ), + form3.actions( + button(cls := "cancel button button-empty", tpe := "button")("Cancel"), + form3.submit(frag("Relocate the thread"))(cls := "button-red") + ) + ) + ) diff --git a/modules/forumSearch/src/main/Env.scala b/modules/forumSearch/src/main/Env.scala index d9b08fa2af131..9cf51e151072a 100644 --- a/modules/forumSearch/src/main/Env.scala +++ b/modules/forumSearch/src/main/Env.scala @@ -13,11 +13,7 @@ import lila.search.spec.Query @Module private class ForumSearchConfig(@ConfigName("paginator.max_per_page") val maxPerPage: MaxPerPage) -final class Env( - appConfig: Configuration, - postApi: lila.core.forum.ForumPostApi, - client: SearchClient -)(using Executor): +final class Env(appConfig: Configuration, client: SearchClient)(using Executor): private val config = appConfig.get[ForumSearchConfig]("forumSearch")(AutoConfig.loader) diff --git a/modules/forumSearch/src/main/package.scala b/modules/forumSearch/src/main/package.scala index 3604898cd0680..d4301e043437d 100644 --- a/modules/forumSearch/src/main/package.scala +++ b/modules/forumSearch/src/main/package.scala @@ -1,7 +1,6 @@ package lila.forumSearch export lila.core.lilaism.Lilaism.{ *, given } -export lila.common.extensions.* private val logger = lila.log("forumSearch") diff --git a/modules/mailer/src/main/AutomaticEmail.scala b/modules/mailer/src/main/AutomaticEmail.scala index ac87b0259fa05..43219ce0e3893 100644 --- a/modules/mailer/src/main/AutomaticEmail.scala +++ b/modules/mailer/src/main/AutomaticEmail.scala @@ -46,16 +46,15 @@ The Lichess team""" import lila.core.i18n.I18nKey s"""${I18nKey.onboarding.welcome.txt()}\n${I18nKey.site.lichessPatronInfo.txt()}""" - def onTitleSet(username: UserStr, title: chess.PlayerTitle, public: Boolean): Funit = { + def onTitleSet(username: UserStr, title: chess.PlayerTitle): Funit = { for user <- userApi.byId(username).orFail(s"No such user $username") emailOption <- userApi.email(user.id) body = alsoSendAsPrivateMessage(user): _ => - val visible = public.so(s"""It is now visible on your profile page: $baseUrl/@/${user.username}.""") s"""Hello, Thank you for confirming your $title title on Lichess. -$visible +It is now visible on your profile page: $baseUrl/@/${user.username}. $regards """ diff --git a/modules/msg/src/main/MsgContact.scala b/modules/msg/src/main/MsgContact.scala index f81d3151d12dd..56dea3465fa51 100644 --- a/modules/msg/src/main/MsgContact.scala +++ b/modules/msg/src/main/MsgContact.scala @@ -5,6 +5,8 @@ import reactivemongo.api.bson.* import lila.core.user.UserMarks import lila.db.dsl.{ *, given } +import lila.core.perm.Permission +import lila.core.perm.Granter private case class Contact( @Key("_id") id: UserId, @@ -13,13 +15,14 @@ private case class Contact( roles: Option[List[String]], createdAt: Instant ): - def isKid = ~kid - def isTroll = marks.exists(_.troll) - def isVerified = roles.exists(_ contains "ROLE_VERIFIED") - def isApiHog = roles.exists(_ contains "ROLE_API_HOG") - def isDaysOld(days: Int) = createdAt.isBefore(nowInstant.minusDays(days)) - def isHoursOld(hours: Int) = createdAt.isBefore(nowInstant.minusHours(hours)) - def isLichess = id.is(UserId.lichess) + def isKid = ~kid + def isTroll = marks.exists(_.troll) + def isVerified = roles.exists(_ contains "ROLE_VERIFIED") + def isApiHog = roles.exists(_ contains "ROLE_API_HOG") + def isDaysOld(days: Int) = createdAt.isBefore(nowInstant.minusDays(days)) + def isHoursOld(hours: Int) = createdAt.isBefore(nowInstant.minusHours(hours)) + def isLichess = id.is(UserId.lichess) + def isGranted(perm: Permission.Selector) = Granter.ofDbKeys(perm, ~roles) private case class Contacts(orig: Contact, dest: Contact): def hasKid = orig.isKid || dest.isKid diff --git a/modules/msg/src/main/MsgPreset.scala b/modules/msg/src/main/MsgPreset.scala index bf87020b5595a..7a412df27a9dc 100644 --- a/modules/msg/src/main/MsgPreset.scala +++ b/modules/msg/src/main/MsgPreset.scala @@ -9,15 +9,20 @@ object MsgPreset: import lila.core.msg.{ MsgPreset as Msg } + private val baseUrl = "https://lichess.org" + def maxFollow(username: UserName, max: Int) = Msg( name = "Follow limit reached!", text = s"""Sorry, you can't follow more than $max players on Lichess. -To follow new players, you must first unfollow some on https://lichess.org/@/$username/following. +To follow new players, you must first unfollow some on $baseUrl/@/$username/following. Thank you for your understanding.""" ) + def forumRelocation(title: String, newUrl: String) = + s"""A moderator has moved your post "$title" to a different subforum. You can find it here: $baseUrl$newUrl.""" + object forumDeletion: val presets = List( @@ -30,12 +35,12 @@ Thank you for your understanding.""" def byModerator = compose("A moderator") - def byTeamLeader(forumId: ForumCategId) = compose(s"A team leader of https://lichess.org/forum/$forumId") + def byTeamLeader(forumId: ForumCategId) = compose(s"A team leader of $baseUrl/forum/$forumId") def byBlogAuthor(user: UserName) = compose(by = s"The community blog author $user") private def compose(by: String)(reason: String, forumPost: String) = - s"""$by deleted the following of your posts for this reason: $reason. Please read Lichess' Forum-Etiquette: https://lichess.org/page/forum-etiquette + s"""$by deleted the following of your posts for this reason: $reason. Please read Lichess' Forum-Etiquette: $baseUrl/page/forum-etiquette ---- $forumPost """ diff --git a/modules/msg/src/main/MsgSecurity.scala b/modules/msg/src/main/MsgSecurity.scala index 3b166ac368271..e28ee1a2ff6bc 100644 --- a/modules/msg/src/main/MsgSecurity.scala +++ b/modules/msg/src/main/MsgSecurity.scala @@ -78,7 +78,8 @@ final private class MsgSecurity( .getOrElse(fuccess(Ok)) .flatMap: case Troll => - destFollowsOrig(contacts).dmap: + (fuccess(contacts.any(_.isGranted(_.PublicMod))) >>| + destFollowsOrig(contacts)).dmap: if _ then TrollFriend else Troll case mute: Mute => destFollowsOrig(contacts).dmap: @@ -141,15 +142,17 @@ final private class MsgSecurity( contactApi.contacts(orig, dest).flatMapz { post(_, isNew) } def post(contacts: Contacts, isNew: Boolean): Fu[Boolean] = - fuccess(!contacts.dest.isLichess && !contacts.any(_.marks.exists(_.isolate))) >>& { - fuccess(Granter.ofDbKeys(_.PublicMod, ~contacts.orig.roles)) >>| { + ( + !contacts.dest.isLichess && + (!contacts.any(_.marks.exists(_.isolate)) || contacts.any(_.isGranted(_.Shadowban))) + ).so: + fuccess(contacts.orig.isGranted(_.PublicMod)) >>| { relationApi.fetchBlocks(contacts.dest.id, contacts.orig.id).not >>& (create(contacts) >>| reply(contacts)) >>& chatPanicAllowed(contacts.orig.id)(userApi.byId) >>& kidCheck(contacts, isNew) >>& userCache.getBotIds.map { botIds => !contacts.userIds.exists(botIds.contains) } } - } private def create(contacts: Contacts): Fu[Boolean] = prefApi.getMessage(contacts.dest.id).flatMap { diff --git a/modules/opening/src/main/OpeningTree.scala b/modules/opening/src/main/OpeningTree.scala index 914289bafb358..98371cac20909 100644 --- a/modules/opening/src/main/OpeningTree.scala +++ b/modules/opening/src/main/OpeningTree.scala @@ -27,7 +27,7 @@ object OpeningTree: lazy val compute: OpeningTree = OpeningDb.shortestLines.values - .map { op => + .map: op => val sections = NameSection.sectionsOf(op.name) sections.toList.mapWithIndex: (name, i) => ( @@ -36,7 +36,6 @@ object OpeningTree: OpeningKey.fromName(OpeningName(sections.take(i + 1).mkString("_"))) ) ) - } .toList .foldLeft(emptyNode)(_.update(_)) .toTree diff --git a/modules/plan/src/main/ui/PlanUi.scala b/modules/plan/src/main/ui/PlanUi.scala index ee745b61324b6..5dcb2afb87cb8 100644 --- a/modules/plan/src/main/ui/PlanUi.scala +++ b/modules/plan/src/main/ui/PlanUi.scala @@ -32,18 +32,19 @@ final class PlanUi(helpers: Helpers)(contactEmail: EmailAddress): ctx.isAuth.option( frag( stripeScript, - frag( - // gotta load the paypal SDK twice, for onetime and subscription :facepalm: - // https://stackoverflow.com/questions/69024268/how-can-i-show-a-paypal-smart-subscription-button-and-a-paypal-smart-capture-but/69024269 - script( - src := s"https://www.paypal.com/sdk/js?client-id=${payPalPublicKey}¤cy=${pricing.currency}$localeParam", - namespaceAttr := "paypalOrder" - ), - script( - src := s"https://www.paypal.com/sdk/js?client-id=${payPalPublicKey}&vault=true&intent=subscription¤cy=${pricing.currency}$localeParam", - namespaceAttr := "paypalSubscription" + pricing.payPalSupportsCurrency.option: + frag( + // gotta load the paypal SDK twice, for onetime and subscription :facepalm: + // https://stackoverflow.com/questions/69024268/how-can-i-show-a-paypal-smart-subscription-button-and-a-paypal-smart-capture-but/69024269 + script( + src := s"https://www.paypal.com/sdk/js?client-id=${payPalPublicKey}¤cy=${pricing.currency}$localeParam", + namespaceAttr := "paypalOrder" + ), + script( + src := s"https://www.paypal.com/sdk/js?client-id=${payPalPublicKey}&vault=true&intent=subscription¤cy=${pricing.currency}$localeParam", + namespaceAttr := "paypalSubscription" + ) ) - ) ) ) .js(ctx.isAuth.option(embedJsUnsafeLoadThen(s"""checkoutStart("$stripePublicKey", $pricingJson)"""))) diff --git a/modules/playban/src/main/PlaybanApi.scala b/modules/playban/src/main/PlaybanApi.scala index 1c160d825b0ca..4535da9676ced 100644 --- a/modules/playban/src/main/PlaybanApi.scala +++ b/modules/playban/src/main/PlaybanApi.scala @@ -2,10 +2,10 @@ package lila.playban import chess.{ Centis, Color, Status } import reactivemongo.api.bson.* +import scalalib.model.Days import lila.common.{ Bus, Uptime } import lila.db.dsl.{ *, given } - import lila.core.msg.{ MsgApi, MsgPreset } import lila.core.game.Source import lila.core.playban.RageSit as RageSitCounter @@ -89,21 +89,18 @@ final class PlaybanApi( game .player(flaggerColor) .userId - .ifTrue { + .ifTrue: ~(for movetimes <- gameApi.computeMoveTimes(game, flaggerColor) lastMovetime <- movetimes.lastOption limit <- unreasonableTime yield lastMovetime.toSeconds >= limit) - } - .map { userId => + .map: userId => (save(Outcome.SitMoving, userId, RageSit.imbalanceInc(game, flaggerColor), game.source) >> propagateSitting(game, userId)).andDo(feedback.sitting(Pov(game, flaggerColor))) - } - IfBlameable(game) { + IfBlameable(game): sitting.orElse(sitMoving).getOrElse(good(game, flaggerColor)) - } private def propagateSitting(game: Game, userId: UserId): Funit = game.tournamentId.so: tourId => @@ -140,9 +137,23 @@ final class PlaybanApi( } private def good(game: Game, loserColor: Color): Funit = - game.player(loserColor).userId.so { - save(Outcome.Good, _, RageSit.redeem(game), game.source) - } + import chess.variant.* + val quickResign = game.status == Status.Resign && + game.durationSeconds.exists(_ < 60) && + game.playedTurns >= game.variant.match + case Standard | Chess960 | Horde => 20 + case Antichess | Crazyhouse | KingOfTheHill => 15 + case ThreeCheck | Atomic | RacingKings => 10 + + if quickResign then + game.userIds.parallelVoid: userId => + save(Outcome.Sandbag, userId, RageSit.Update.Noop, game.source) + else + game + .player(loserColor) + .userId + .so: userId => + save(Outcome.Good, userId, RageSit.redeem(game), game.source) // memorize users without any ban to save DB reads private val cleanUserIds = scalalib.cache.ExpireSetMemo[UserId](30 minutes) @@ -222,16 +233,16 @@ final class PlaybanApi( if outcome == Outcome.Good then fuccess(withOutcome) else for - createdAt <- userApi.createdAtById(userId).orFail(s"Missing user creation date $userId") - withBan <- legiferate(withOutcome, createdAt, source) + age <- userApi.accountAge(userId) + withBan <- legiferate(withOutcome, age, source) yield withBan _ <- registerRageSit(withBan, rsUpdate) yield () }.void.logFailure(lila.log("playban")) - private def legiferate(record: UserRecord, accCreatedAt: Instant, source: Option[Source]): Fu[UserRecord] = + private def legiferate(record: UserRecord, age: Days, source: Option[Source]): Fu[UserRecord] = record - .bannable(accCreatedAt) + .bannable(age) .ifFalse(record.banInEffect) .so: ban => lila.mon.playban.ban.count.increment() diff --git a/modules/playban/src/main/model.scala b/modules/playban/src/main/model.scala index 879c9fecbbb1e..80087d7a1e900 100644 --- a/modules/playban/src/main/model.scala +++ b/modules/playban/src/main/model.scala @@ -4,6 +4,7 @@ import play.api.libs.json.* import lila.common.Json.given import lila.core.playban.RageSit as RageSitCounter +import scalalib.model.Days case class UserRecord( _id: UserId, @@ -19,41 +20,52 @@ case class UserRecord( def banInEffect = bans.lastOption.exists(_.inEffect) def banMinutes = bans.lastOption.map(_.remainingMinutes) + def bansThisWeek = + val since = nowInstant.minusDays(7) + bans.count(_.date.isAfter(since)) def nbOutcomes = outcomes.size def badOutcomeScore: Float = outcomes.collect { - case Outcome.NoPlay | Outcome.Abort => .7f + case Outcome.Sandbag => .7f + case Outcome.NoPlay | Outcome.Abort => .8f case o if o != Outcome.Good => 1 } sum - def badOutcomeRatio: Float = if bans.sizeIs < 3 then 0.4f else 0.3f + def badOutcomeTolerance(age: Days): Float = + if age <= 1 then 0.3f + else if bans.sizeIs < 3 then 0.4f + else 0.3f def minBadOutcomes: Int = - bans.size match + bansThisWeek match case 0 | 1 => 4 case 2 | 3 => 3 case _ => 2 - def badOutcomesStreakSize: Int = - bans.size match - case 0 => 6 - case 1 | 2 => 5 - case _ => 4 + def badOutcomesStreakSize(age: Days): Int = + if age <= 1 + then 3 + else if bans.size == 0 then 6 + else if bans.size < 3 then 5 + else if bans.size < 10 then 4 + else 3 - def bannable(accountCreationDate: Instant): Option[TempBan] = { + def bannable(age: Days): Option[TempBan] = { rageSitRecidive || { outcomes.lastOption.exists(_ != Outcome.Good) && { // too many bad overall - badOutcomeScore >= ((badOutcomeRatio * nbOutcomes).atLeast(minBadOutcomes.toFloat)) || { + val toleranceRatio = badOutcomeTolerance(age) + badOutcomeScore >= ((toleranceRatio * nbOutcomes).atLeast(minBadOutcomes.toFloat)) || { // bad result streak - outcomes.sizeIs >= badOutcomesStreakSize && - outcomes.takeRight(badOutcomesStreakSize).forall(Outcome.Good !=) + val streakSize = badOutcomesStreakSize(age) + outcomes.sizeIs >= streakSize && + outcomes.takeRight(streakSize).forall(Outcome.Good !=) } } } - }.option(TempBan.make(bans, accountCreationDate)) + }.option(TempBan.make(bans, age)) def rageSitRecidive = outcomes.lastOption.exists(Outcome.rageSitLike.contains) && { @@ -91,7 +103,7 @@ object TempBan: * - 0 - 3 days: linear scale from 3x to 1x * - >3 days quick drop off Account less than 3 days old --> 2x the usual time */ - def make(bans: Vector[TempBan], accountCreationDate: Instant): TempBan = + def make(bans: Vector[TempBan], age: Days): TempBan = make { (bans.lastOption .so { prev => @@ -99,7 +111,7 @@ object TempBan: case h if h < 72 => prev.mins * (132 - h) / 60 case h => (55.6 * prev.mins / (Math.pow(5.56 * prev.mins - 54.6, h / 720) + 54.6)).toInt } - .atLeast(baseMinutes)) * (if accountCreationDate.plusDays(3).isAfterNow then 2 else 1) + .atLeast(baseMinutes)) * (if age <= 3 then 2 else 1) } enum Outcome(val id: Int, val name: String): diff --git a/modules/playban/src/test/PlaybanTest.scala b/modules/playban/src/test/PlaybanTest.scala new file mode 100644 index 0000000000000..025b64f76126b --- /dev/null +++ b/modules/playban/src/test/PlaybanTest.scala @@ -0,0 +1,36 @@ +package lila.playban + +import scalalib.model.Days + +class PlaybanTest extends munit.FunSuite: + + val userId = UserId("user") + val brandNew = Days(0) + + test("empty"): + val rec = UserRecord(userId, none, none, none) + assertEquals(rec.bannable(brandNew), None) + + test("new one abort"): + val rec = UserRecord(userId, Vector(Outcome.Abort).some, none, none) + assertEquals(rec.bannable(brandNew), None) + + test("new 2 aborts"): + val rec = UserRecord(userId, Vector.fill(2)(Outcome.Abort).some, none, none) + assertEquals(rec.bannable(brandNew), None) + + test("new 3 aborts"): + val rec = UserRecord(userId, Vector.fill(3)(Outcome.Abort).some, none, none) + assert(rec.bannable(brandNew).isDefined) + + test("good and aborts"): + import Outcome.* + val outcomes = Vector(Good, Abort, Good, Abort, Abort) + val rec = UserRecord(userId, outcomes.some, none, none) + assert(rec.bannable(brandNew).isEmpty) + + test("sandbag and aborts"): + import Outcome.* + val outcomes = Vector(Sandbag, Sandbag, Abort, Sandbag, Abort, Abort) + val rec = UserRecord(userId, outcomes.some, none, none) + assert(rec.bannable(brandNew).isDefined) diff --git a/modules/pref/src/main/Pref.scala b/modules/pref/src/main/Pref.scala index 534c960e01ff4..aa17bec4cc1c0 100644 --- a/modules/pref/src/main/Pref.scala +++ b/modules/pref/src/main/Pref.scala @@ -329,17 +329,20 @@ object Pref: val NONE = 0 val INSIDE = 1 val OUTSIDE = 2 + val ALL = 3 val choices = Seq( NONE -> "No", INSIDE -> "Inside the board", - OUTSIDE -> "Outside the board" + OUTSIDE -> "Outside the board", + ALL -> "Inside all squares of the board" ) def classOf(v: Int) = v match case INSIDE => "in" case OUTSIDE => "out" + case ALL => "all" case _ => "no" object Replay: diff --git a/modules/pref/src/main/PrefForm.scala b/modules/pref/src/main/PrefForm.scala index 0981760c569f6..d655c80980e35 100644 --- a/modules/pref/src/main/PrefForm.scala +++ b/modules/pref/src/main/PrefForm.scala @@ -47,6 +47,7 @@ object PrefForm: val submitMove = "submitMove" -> bitCheckedNumber(Pref.SubmitMove.choices) val confirmResign = "confirmResign" -> checkedNumber(Pref.ConfirmResign.choices) val moretime = "moretime" -> checkedNumber(Pref.Moretime.choices) + val clockSound = "clockSound" -> booleanNumber val ratings = "ratings" -> booleanNumber val flairs = "flairs" -> boolean val follow = "follow" -> booleanNumber diff --git a/modules/pref/src/main/PrefSingleChange.scala b/modules/pref/src/main/PrefSingleChange.scala index b35e163e0a073..e4c9a270b5b30 100644 --- a/modules/pref/src/main/PrefSingleChange.scala +++ b/modules/pref/src/main/PrefSingleChange.scala @@ -51,6 +51,8 @@ object PrefSingleChange: _.copy(confirmResign = v), changing(_.moretime): v => _.copy(moretime = v), + changing(_.clockSound): v => + _.copy(clockSound = v == 1), changing(_.ratings): v => _.copy(ratings = v), changing(_.follow): v => diff --git a/modules/pref/src/main/ui/AccountPages.scala b/modules/pref/src/main/ui/AccountPages.scala index 0ea4928445e7f..6be7b50d1b7b7 100644 --- a/modules/pref/src/main/ui/AccountPages.scala +++ b/modules/pref/src/main/ui/AccountPages.scala @@ -78,8 +78,7 @@ final class AccountPages(helpers: Helpers, ui: AccountUi, flagApi: lila.core.use form3.group(form("location"), trans.site.location(), half = true)(form3.input(_)) ), form3.split( - form3.group(form("firstName"), trans.site.firstName(), half = true)(form3.input(_)), - form3.group(form("lastName"), trans.site.lastName(), half = true)(form3.input(_)) + form3.group(form("realName"), trans.site.realName(), half = true)(form3.input(_)) ), form3.split( List("fide", "uscf", "ecf", "rcf", "cfc", "dsb").map: rn => diff --git a/modules/pref/src/main/ui/PrefHelper.scala b/modules/pref/src/main/ui/PrefHelper.scala index cec233b50b8b4..ed98f7a37e5b2 100644 --- a/modules/pref/src/main/ui/PrefHelper.scala +++ b/modules/pref/src/main/ui/PrefHelper.scala @@ -24,7 +24,8 @@ trait PrefHelper: List( (Pref.Coords.NONE, trans.site.no.txt()), (Pref.Coords.INSIDE, trans.site.insideTheBoard.txt()), - (Pref.Coords.OUTSIDE, trans.site.outsideTheBoard.txt()) + (Pref.Coords.OUTSIDE, trans.site.outsideTheBoard.txt()), + (Pref.Coords.ALL, trans.site.allSquaresOfTheBoard.txt()) ) def translatedMoveListWhilePlayingChoices(using Translate) = diff --git a/modules/puzzle/src/main/JsonView.scala b/modules/puzzle/src/main/JsonView.scala index 6de4c4053895a..a5f75a3c29f1d 100644 --- a/modules/puzzle/src/main/JsonView.scala +++ b/modules/puzzle/src/main/JsonView.scala @@ -44,6 +44,10 @@ final class JsonView( .add("chapter" -> a.asTheme.flatMap(PuzzleTheme.studyChapterIds.get)) .add("opening" -> a.opening.map: op => Json.obj("key" -> op.key, "name" -> op.name)) + .add("openingAbstract" -> a.match + case op: PuzzleAngle.Opening => op.isAbstract + case _ => false + ) ) def userJson(using me: Option[Me], perf: Perf) = me.map: me => diff --git a/modules/puzzle/src/main/PuzzleAngle.scala b/modules/puzzle/src/main/PuzzleAngle.scala index 52cc948e49d85..131f573d60dfa 100644 --- a/modules/puzzle/src/main/PuzzleAngle.scala +++ b/modules/puzzle/src/main/PuzzleAngle.scala @@ -29,6 +29,7 @@ object PuzzleAngle: opKey => SimpleOpening(opKey).map(_.ref.key) ) .flatMap(OpeningDb.shortestLines.get) + def isAbstract = opening.isEmpty val name = I18nKey(openingName) def description = I18nKey(s"From games with the opening: $openingName") def asTheme = none diff --git a/modules/relay/src/main/BSONHandlers.scala b/modules/relay/src/main/BSONHandlers.scala index 4c6da447cce59..551ed360a03d8 100644 --- a/modules/relay/src/main/BSONHandlers.scala +++ b/modules/relay/src/main/BSONHandlers.scala @@ -2,6 +2,7 @@ package lila.relay import reactivemongo.api.bson.* +import lila.db.BSON import lila.db.dsl.{ *, given } object BSONHandlers: @@ -10,36 +11,22 @@ object BSONHandlers: given BSONHandler[RelayTeamsTextarea] = stringAnyValHandler(_.text, RelayTeamsTextarea(_)) import RelayRound.Sync - import Sync.{ Upstream, UpstreamIds, UpstreamUrl, UpstreamLcc, UpstreamUrls, FetchableUpstream } - given upstreamUrlHandler: BSONDocumentHandler[UpstreamUrl] = Macros.handler - given upstreamLccHandler: BSONDocumentHandler[UpstreamLcc] = Macros.handler - given BSONHandler[FetchableUpstream] = tryHandler( - { - case d: BSONDocument if d.contains("url") => upstreamUrlHandler.readTry(d) - case d: BSONDocument if d.contains("lcc") => upstreamLccHandler.readTry(d) - }, - { - case url: UpstreamUrl => upstreamUrlHandler.writeTry(url).get - case lcc: UpstreamLcc => upstreamLccHandler.writeTry(lcc).get - } - ) - given upstreamUrlsHandler: BSONDocumentHandler[UpstreamUrls] = Macros.handler - given upstreamIdsHandler: BSONDocumentHandler[UpstreamIds] = Macros.handler - - given BSONHandler[Upstream] = tryHandler( - { - case d: BSONDocument if d.contains("url") => upstreamUrlHandler.readTry(d) - case d: BSONDocument if d.contains("lcc") => upstreamLccHandler.readTry(d) - case d: BSONDocument if d.contains("urls") => upstreamUrlsHandler.readTry(d) - case d: BSONDocument if d.contains("ids") => upstreamIdsHandler.readTry(d) - }, - { - case url: UpstreamUrl => upstreamUrlHandler.writeTry(url).get - case lcc: UpstreamLcc => upstreamLccHandler.writeTry(lcc).get - case urls: UpstreamUrls => upstreamUrlsHandler.writeTry(urls).get - case ids: UpstreamIds => upstreamIdsHandler.writeTry(ids).get - } - ) + import Sync.Upstream + given upstreamUrlHandler: BSONDocumentHandler[Upstream.Url] = Macros.handler + given upstreamUrlsHandler: BSONDocumentHandler[Upstream.Urls] = Macros.handler + given upstreamIdsHandler: BSONDocumentHandler[Upstream.Ids] = Macros.handler + + given BSONHandler[Upstream] = new BSON[Upstream]: + def reads(r: BSON.Reader): Upstream = + if r.contains("url") then upstreamUrlHandler.readTry(r.doc).get + else if r.contains("urls") then upstreamUrlsHandler.readTry(r.doc).get + else upstreamIdsHandler.readTry(r.doc).get + def writes(w: BSON.Writer, up: Upstream) = + val doc = up match + case url: Upstream.Url => upstreamUrlHandler.writeTry(url).get + case urls: Upstream.Urls => upstreamUrlsHandler.writeTry(urls).get + case ids: Upstream.Ids => upstreamIdsHandler.writeTry(ids).get + doc ++ up.roundIds.some.filter(_.nonEmpty).so(ids => $doc("roundIds" -> ids)) import SyncLog.Event given BSONDocumentHandler[Event] = Macros.handler @@ -53,8 +40,9 @@ object BSONHandlers: given BSONDocumentHandler[RelayRound] = Macros.handler - // private given BSONHandler[play.api.i18n.Lang] = langByCodeHandler given BSONDocumentHandler[RelayTour.Spotlight] = Macros.handler + given BSONDocumentHandler[RelayTour.Info] = Macros.handler + given BSONDocumentHandler[RelayTour.Dates] = Macros.handler given tourHandler: BSONDocumentHandler[RelayTour] = Macros.handler given BSONDocumentHandler[RelayTour.IdName] = Macros.handler diff --git a/modules/relay/src/main/JsonView.scala b/modules/relay/src/main/JsonView.scala index 40110e054f143..a510328003003 100644 --- a/modules/relay/src/main/JsonView.scala +++ b/modules/relay/src/main/JsonView.scala @@ -24,20 +24,26 @@ final class JsonView( given Writes[Option[RelayTour.Tier]] = Writes: t => JsString(t.flatMap(RelayTour.Tier.keys.get) | "user") + given OWrites[RelayTour.Info] = Json.writes + + given Writes[RelayTour.Dates] = Writes: ds => + JsArray(List(ds.start.some, ds.end).flatten.map(Json.toJson)) + given OWrites[RelayTour] = OWrites: t => Json .obj( - "id" -> t.id, - "name" -> t.name, - "slug" -> t.slug, - "description" -> t.description, - "createdAt" -> t.createdAt, - "url" -> s"$baseUrl${t.path}" + "id" -> t.id, + "name" -> t.name, + "slug" -> t.slug, + "info" -> t.info, + "createdAt" -> t.createdAt, + "url" -> s"$baseUrl${t.path}" ) .add("tier" -> t.tier) + .add("dates" -> t.dates) .add("image" -> t.image.map(id => RelayTour.thumbnail(picfitUrl, id, _.Size.Large))) - given OWrites[RelayTour.IdName] = Json.writes[RelayTour.IdName] + given OWrites[RelayTour.IdName] = Json.writes given OWrites[RelayGroup.WithTours] = OWrites: g => Json.obj( @@ -66,15 +72,15 @@ final class JsonView( case tr: WithLastRound => Json .obj( - "tour" -> fullTour(tr.tour), - "lastRound" -> withUrl(tr.round.withTour(tr.tour), withTour = false) + "tour" -> fullTour(tr.tour), + "round" -> withUrl(tr.round.withTour(tr.tour), withTour = false) ) .add("group" -> tr.group) case tr: ActiveWithSomeRounds => Json .obj( - "tour" -> fullTour(tr.tour), - "lastRound" -> withUrl(tr) + "tour" -> fullTour(tr.tour), + "round" -> withUrl(tr) ) .add("group" -> tr.group) @@ -92,9 +98,13 @@ final class JsonView( def withUrlAndPreviews( rt: RelayRound.WithTourAndStudy, previews: ChapterPreview.AsJsons, - group: Option[RelayGroup.WithTours] + group: Option[RelayGroup.WithTours], + targetRound: Option[RelayRound.WithTour] )(using Option[Me]): JsObject = - myRound(rt) ++ Json.obj("games" -> previews).add("group" -> group) + myRound(rt) ++ Json + .obj("games" -> previews) + .add("group" -> group) + .add("targetRound" -> targetRound.map(withUrl(_, true))) def sync(round: RelayRound) = Json.toJsObject(round.sync) @@ -120,6 +130,7 @@ final class JsonView( JsonView.JsData( relay = fullTourWithRounds(trs, group) .add("sync" -> (canContribute.so(trs.rounds.find(_.id == currentRoundId).map(_.sync)))) + .add("lcc", trs.rounds.find(_.id == currentRoundId).map(_.sync.upstream.exists(_.hasLcc))) .add("isSubscribed" -> isSubscribed) .add("videoUrls" -> videoUrls) .add("pinned" -> pinned.map: (id, name, image) => @@ -165,7 +176,6 @@ object JsonView: given OWrites[RelayStats.RoundStats] = OWrites: r => Json.obj( - "round" -> r.round, "viewers" -> r.viewers.map: (minute, crowd) => Json.arr(minute * 60, crowd) ) @@ -179,7 +189,6 @@ object JsonView: ) .add("delay" -> s.delay) ++ s.upstream.so: - case Sync.UpstreamUrl(url) => Json.obj("url" -> url) - case Sync.UpstreamLcc(url, round) => Json.obj("url" -> url, "round" -> round) - case Sync.UpstreamUrls(urls) => Json.obj("urls" -> urls.map(_.formUrl)) - case Sync.UpstreamIds(ids) => Json.obj("ids" -> ids) + case Sync.Upstream.Url(url) => Json.obj("url" -> url) + case Sync.Upstream.Urls(urls) => Json.obj("urls" -> urls) + case Sync.Upstream.Ids(ids) => Json.obj("ids" -> ids) diff --git a/modules/relay/src/main/RelayApi.scala b/modules/relay/src/main/RelayApi.scala index ca32574e5ab97..a695190b17655 100644 --- a/modules/relay/src/main/RelayApi.scala +++ b/modules/relay/src/main/RelayApi.scala @@ -12,7 +12,17 @@ import lila.memo.{ CacheApi, PicfitApi } import lila.relay.RelayRound.{ WithTour, Sync } import lila.core.perm.Granter import lila.core.study.data.StudyName -import lila.study.{ Settings, Study, StudyApi, StudyId, StudyMaker, StudyRepo, StudyTopic } +import lila.study.{ + Settings, + Study, + StudyApi, + StudyId, + StudyMaker, + StudyRepo, + StudyTopic, + StudyMember, + StudyMembers +} final class RelayApi( roundRepo: RelayRoundRepo, @@ -53,8 +63,14 @@ final class RelayApi( byIdWithTour(id).flatMapz(rt => formNavigation(rt).dmap(some)) def formNavigation(rt: RelayRound.WithTour): Fu[(RelayRound, ui.FormNavigation)] = - formNavigation(rt.tour).map: nav => - (rt.round, nav.copy(round = rt.round.id.some)) + for + nav <- formNavigation(rt.tour) + sourceRound <- rt.round.sync.upstream.flatMap(_.roundId).so(byIdWithTour) + targetRound <- officialTarget(rt.round) + yield ( + rt.round, + nav.copy(roundId = rt.round.id.some, sourceRound = sourceRound, targetRound = targetRound) + ) def formNavigation(tour: RelayTour): Fu[ui.FormNavigation] = for group <- withTours.get(tour.id) @@ -88,14 +104,34 @@ final class RelayApi( def withRounds(tour: RelayTour) = roundRepo.byTourOrdered(tour.id).dmap(tour.withRounds) - def denormalizeTourActive(tourId: RelayTourId): Funit = + def denormalizeTour(tourId: RelayTourId): Funit = val unfinished = RelayRoundRepo.selectors.tour(tourId) ++ $doc("finished" -> false) for active <- roundRepo.coll.exists(unfinished) live <- active.so(roundRepo.coll.exists(unfinished ++ $doc("startedAt".$exists(true)))) - _ <- tourRepo.setActive(tourId, active, live) + dates <- computeDates(tourId) + _ <- tourRepo.denormalize(tourId, active, live, dates) yield () + private def computeDates(tourId: RelayTourId): Fu[Option[RelayTour.Dates]] = + roundRepo.coll + .aggregateOne(): framework => + import framework.* + Match($doc("tourId" -> tourId)) -> List( + Project($doc("at" -> $doc("$ifNull" -> $arr("$startsAt", "$startedAt")))), + Sort(Ascending("at")), + Group(BSONNull)("at" -> PushField("at")), + Project($doc("start" -> $doc("$first" -> "$at"), "end" -> $doc("$last" -> "$at"))) + ) + .map: + _.flatMap: doc => + for + start <- doc.getAsOpt[Instant]("start") + end <- doc.getAsOpt[Instant]("end") + singleDay = end.isBefore(start.plusDays(1)) + endMaybe = Option.when(!singleDay)(end) + yield RelayTour.Dates(start, endMaybe) + object countOwnedByUser: private val cache = cacheApi[UserId, Int](16_384, "relay.nb.owned"): _.expireAfterWrite(5.minutes).buildAsyncFuture(tourRepo.countByOwner(_, false)) @@ -182,7 +218,7 @@ final class RelayApi( $id(tour.id), $setsAndUnsets( "name" -> tour.name.some, - "description" -> tour.description.some, + "info" -> tour.info.some, "markup" -> tour.markup, "tier" -> tour.tier, "autoLeaderboard" -> tour.autoLeaderboard.some, @@ -204,48 +240,54 @@ final class RelayApi( val canGroup = fuccess(Granter(_.StudyAdmin)) >>| tourRepo.isOwnerOfAll(me.userId, data.tourIds) canGroup.flatMapz(groupRepo.update(tour.id, data)) - def create(data: RelayRoundForm.Data, tour: RelayTour)(using me: Me): Fu[RelayRound.WithTourAndStudy] = - roundRepo - .lastByTour(tour) - .flatMapz: last => - studyRepo.byId(last.studyId) - .flatMap: lastStudy => - import lila.study.{ StudyMember, StudyMembers } - val relay = data.make(me, tour) - for - study <- studyApi - .create( - StudyMaker.ImportGame( - id = relay.studyId.some, - name = relay.name.into(StudyName).some, - settings = lastStudy - .fold( - Settings.init - .copy( - chat = Settings.UserSelection.Everyone, - sticky = false - ) - )(_.settings) - .some, - from = Study.From.Relay(none).some - ), - me, - withRatings = true, - _.copy( - members = - lastStudy.fold(StudyMembers.empty)(_.members) + StudyMember(me, StudyMember.Role.Write) - ) + def create(data: RelayRoundForm.Data, tour: RelayTour)(using me: Me): Fu[RelayRound.WithTourAndStudy] = for + last <- roundRepo.lastByTour(tour) + lastStudy <- last.so(r => studyRepo.byId(r.studyId)) + relay <- copyRoundSourceSettings(data.make(me, tour)) + importGame = StudyMaker.ImportGame( + id = relay.studyId.some, + name = relay.name.into(StudyName).some, + settings = lastStudy + .fold( + Settings.init + .copy( + chat = Settings.UserSelection.Everyone, + sticky = false ) - .orFail(s"Can't create study for relay $relay") - _ <- roundRepo.coll.insert.one(relay) - _ <- tourRepo.setActive(tour.id, true, relay.hasStarted) - _ <- studyApi.addTopics(relay.studyId, List(StudyTopic.broadcast.value)) - yield relay.withTour(tour).withStudy(study.study) + )(_.settings) + .some, + from = Study.From.Relay(none).some + ) + study <- studyApi + .create( + importGame, + me, + withRatings = true, + _.copy( + members = lastStudy.fold(StudyMembers.empty)(_.members) + StudyMember(me, StudyMember.Role.Write) + ) + ) + .orFail(s"Can't create study for relay $relay") + _ <- roundRepo.coll.insert.one(relay) + dates <- computeDates(tour.id) + _ <- tourRepo.denormalize(tour.id, true, relay.hasStarted, dates) + _ <- studyApi.addTopics(relay.studyId, List(StudyTopic.broadcast.value)) + yield relay.withTour(tour).withStudy(study.study) + + private def copyRoundSourceSettings(relay: RelayRound): Fu[RelayRound] = + relay.sync.upstream + .flatMap(_.roundId) + .ifTrue(relay.startsAt.isEmpty) + .so(byId) + .map: + _.fold(relay): sourceRound => + relay.copy(startsAt = sourceRound.startsAt) def requestPlay(id: RelayRoundId, v: Boolean): Funit = WithRelay(id): relay => relay.sync.upstream.collect: - case f: Sync.FetchableUpstream => formatApi.refresh(f) + case Sync.Upstream.Url(url) => formatApi.refresh(url) + case Sync.Upstream.Urls(urls) => urls.foreach(formatApi.refresh) isOfficial(relay.id).flatMap: official => update(relay): r => if v @@ -257,16 +299,17 @@ final class RelayApi( byId(round.id).orFail(s"Relay round ${round.id} not found").flatMap(update(_)(f)) def update(from: RelayRound)(f: Update[RelayRound]): Fu[RelayRound] = - val round = f(from).pipe: r => + val updated = f(from).pipe: r => if r.sync.upstream != from.sync.upstream then r.withSync(_.clearLog) else r - if round == from then fuccess(round) + if updated == from then fuccess(from) else for - _ <- (from.name != round.name).so(studyApi.rename(round.studyId, round.name.into(StudyName))) - _ <- roundRepo.coll.update.one($id(round.id), round).void + round <- copyRoundSourceSettings(updated) + _ <- (from.name != round.name).so(studyApi.rename(round.studyId, round.name.into(StudyName))) + _ <- roundRepo.coll.update.one($id(round.id), round).void _ <- (round.sync.playing != from.sync.playing) .so(sendToContributors(round.id, "relaySync", jsonView.sync(round))) - _ <- (round.stateHash != from.stateHash).so(denormalizeTourActive(round.tourId)) + _ <- denormalizeTour(round.tourId) yield round.sync.log.events.lastOption .ifTrue(round.sync.log != from.sync.log) @@ -274,12 +317,31 @@ final class RelayApi( sendToContributors(round.id, "relayLog", Json.toJsObject(event)) round + def syncTargetsOfSource(source: RelayRound): Funit = + (!source.sync.upstream.exists(_.isRound)).so: // prevent chaining (and circular!) round updates + roundRepo.syncTargetsOfSource(source.id) + + def officialTarget(source: RelayRound): Fu[Option[WithTour]] = + source.sync.isPush.so: + roundRepo.coll + .aggregateOne(): framework => + import framework.* + Match($doc("sync.upstream.roundIds" -> source.id)) -> List( + PipelineOperator(tourRepo.lookup("tourId")), + UnwindField("tour"), + Match($doc("tour.tier".$exists(true))), + Sort(Descending("tour.tier"), Descending("tour.createdAt")), + Limit(1) + ) + .map(_.flatMap(readRoundWithTour)) + def reset(old: RelayRound)(using me: Me): Funit = WithRelay(old.id) { relay => for _ <- studyApi.deleteAllChapters(relay.studyId, me) + _ <- roundRepo.coll.updateField($id(relay.id), "finished", false) _ <- old.hasStartedEarly.so: - roundRepo.coll.update.one($id(relay.id), $set("finished" -> false) ++ $unset("startedAt")).void + roundRepo.coll.unsetField($id(relay.id), "startedAt").void _ <- roundRepo.coll.update.one($id(relay.id), $set("sync.log" -> $arr())) yield leaderboard.invalidate(relay.tourId) } >> requestPlay(old.id, v = true) @@ -288,7 +350,7 @@ final class RelayApi( byIdWithTour(roundId).flatMapz: rt => for _ <- roundRepo.coll.delete.one($id(rt.round.id)) - _ <- denormalizeTourActive(rt.tour.id) + _ <- denormalizeTour(rt.tour.id) yield rt.tour.some def deleteTourIfOwner(tour: RelayTour)(using me: Me): Fu[Boolean] = @@ -397,8 +459,8 @@ final class RelayApi( .$lt(nowInstant.plusSeconds(RelayDelay.maxSeconds.value)) .$gt(nowInstant.minusDays(1)), // bit late now "startedAt".$exists(false), - "sync.until".$exists(false), - "sync.upstream".$exists(true) + "sync.upstream".$exists(true), + $or("sync.until".$exists(false), "sync.until".$lt(nowInstant)) ) ) .flatMap: diff --git a/modules/relay/src/main/RelayDelay.scala b/modules/relay/src/main/RelayDelay.scala index 2666e314861bb..ce89a2c0495eb 100644 --- a/modules/relay/src/main/RelayDelay.scala +++ b/modules/relay/src/main/RelayDelay.scala @@ -1,12 +1,11 @@ package lila.relay import chess.format.pgn.PgnStr - +import io.mola.galimatias.URL import scalalib.model.Seconds import lila.db.dsl.{ *, given } import lila.memo.CacheApi -import lila.relay.RelayRound.Sync.FetchableUpstream import lila.study.MultiPgn final private class RelayDelay(colls: RelayColls)(using Executor): @@ -14,11 +13,11 @@ final private class RelayDelay(colls: RelayColls)(using Executor): import RelayDelay.* def apply( - url: FetchableUpstream, + url: URL, round: RelayRound, - doFetchUrl: (FetchableUpstream, Max) => Fu[RelayGames] + doFetchUrl: URL => Fu[RelayGames] ): Fu[RelayGames] = - dedupCache(url, round, () => doFetchUrl(url, RelayFetch.maxChapters)) + dedupCache(url, round, () => doFetchUrl(url)) .flatMap: latest => round.sync.delay match case Some(delay) if delay > 0 => store.get(url, delay).map(_ | latest.map(_.resetToSetup)) @@ -31,16 +30,17 @@ final private class RelayDelay(colls: RelayColls)(using Executor): private val cache = CacheApi.scaffeineNoScheduler .initialCapacity(8) .maximumSize(128) - .build[FetchableUpstream, GamesSeenBy]() + .build[String, GamesSeenBy]() .underlying - def apply(url: FetchableUpstream, round: RelayRound, doFetch: () => Fu[RelayGames]) = + def apply(url: URL, round: RelayRound, doFetch: () => Fu[RelayGames]) = cache.asMap .compute( - url, + url.toString, (_, v) => Option(v) match case Some(GamesSeenBy(games, seenBy)) if !seenBy(round.id) => + lila.mon.relay.dedup.increment() GamesSeenBy(games, seenBy + round.id) case _ => val futureGames = doFetch().addEffect: games => @@ -51,31 +51,31 @@ final private class RelayDelay(colls: RelayColls)(using Executor): private object store: - private def idOf(upstream: FetchableUpstream, at: Instant) = s"${upstream.formUrl} ${at.toSeconds}" - private val longPast = java.time.Instant.ofEpochMilli(0) + private def idOf(url: URL, at: Instant) = s"$url ${at.toSeconds}" + private val longPast = java.time.Instant.ofEpochMilli(0) - def putIfNew(upstream: FetchableUpstream, games: RelayGames): Funit = + def putIfNew(url: URL, games: RelayGames): Funit = val newPgn = RelayGame.iso.from(games).toPgnStr - getLatestPgn(upstream).flatMap: + getLatestPgn(url).flatMap: case Some(latestPgn) if latestPgn == newPgn => funit case _ => val now = nowInstant - val doc = $doc("_id" -> idOf(upstream, now), "at" -> now, "pgn" -> newPgn) + val doc = $doc("_id" -> idOf(url, now), "at" -> now, "pgn" -> newPgn) colls.delay: _.insert.one(doc).void - def get(upstream: FetchableUpstream, delay: Seconds): Fu[Option[RelayGames]] = - getPgn(upstream, delay).map2: pgn => + def get(url: URL, delay: Seconds): Fu[Option[RelayGames]] = + getPgn(url, delay).map2: pgn => RelayGame.iso.to(MultiPgn.split(pgn, Max(999))) - private def getLatestPgn(upstream: FetchableUpstream): Fu[Option[PgnStr]] = - getPgn(upstream, Seconds(0)) + private def getLatestPgn(url: URL): Fu[Option[PgnStr]] = + getPgn(url, Seconds(0)) - private def getPgn(upstream: FetchableUpstream, delay: Seconds): Fu[Option[PgnStr]] = + private def getPgn(url: URL, delay: Seconds): Fu[Option[PgnStr]] = colls.delay: _.find( $doc( - "_id".$gt(idOf(upstream, longPast)).$lte(idOf(upstream, nowInstant.minusSeconds(delay.value))) + "_id".$gt(idOf(url, longPast)).$lte(idOf(url, nowInstant.minusSeconds(delay.value))) ), $doc("pgn" -> true).some ).sort($sort.desc("_id")) diff --git a/modules/relay/src/main/RelayFetch.scala b/modules/relay/src/main/RelayFetch.scala index 0271e303e391b..0302a4c849a83 100644 --- a/modules/relay/src/main/RelayFetch.scala +++ b/modules/relay/src/main/RelayFetch.scala @@ -25,8 +25,10 @@ final private class RelayFetch( delayer: RelayDelay, fidePlayers: RelayFidePlayerApi, gameRepo: GameRepo, + studyChapterRepo: lila.study.ChapterRepo, pgnDump: PgnDump, gameProxy: lila.core.game.GameProxy, + cacheApi: CacheApi, onlyIds: Option[List[RelayTourId]] = None )(using Executor, Scheduler, lila.core.i18n.Translator)(using mode: play.api.Mode): @@ -42,7 +44,7 @@ final private class RelayFetch( LilaScheduler( "RelayFetch.user", - _.Every(if mode.isDev then 2.seconds else 750 millis), + _.Every(if mode.isDev then 2.seconds else 879 millis), _.AtMost(10 seconds), _.Delay(if mode.isDev then 2.second else 33 seconds) ): @@ -50,27 +52,28 @@ final private class RelayFetch( private val maxRelaysToSync = Max(50) - private def syncRelays(official: Boolean): Funit = - val relays = + private def syncRelays(official: Boolean): Funit = for + relays <- if official then api.toSyncOfficial(maxRelaysToSync, onlyIds) else api.toSyncUser(maxRelaysToSync, onlyIds) - relays - .flatMap: relays => - lila.mon.relay.ongoing(official).update(relays.size) - relays - .parallelVoid: rt => - if rt.round.sync.ongoing then - processRelay(rt).flatMap: updating => - api.reFetchAndUpdate(rt.round)(updating.reRun) - else if rt.round.hasStarted then - logger.info(s"Finish by lack of activity ${rt.round}") - api.update(rt.round)(_.finish) - else if rt.round.shouldGiveUp then - val msg = "Finish for lack of start" - logger.info(s"$msg ${rt.round}") - if rt.tour.official then irc.broadcastError(rt.round.id, rt.fullName, msg) - api.update(rt.round)(_.finish) - else funit + _ <- relays.parallelVoid(syncRelay) + yield lila.mon.relay.ongoing(official).update(relays.size) + + private def syncRelay(rt: RelayRound.WithTour): Funit = + if rt.round.sync.ongoing then + processRelay(rt).flatMap: updating => + api.reFetchAndUpdate(rt.round)(updating.reRun).void + else if rt.round.hasStarted then + logger.info(s"Finish by lack of activity ${rt.round}") + api.update(rt.round)(_.finish).void + else if rt.round.shouldGiveUp then + val msg = "Finish for lack of start" + logger.info(s"$msg ${rt.round}") + if rt.tour.official then irc.broadcastError(rt.round.id, rt.fullName, msg) + api.update(rt.round)(_.finish).void + else + logger.info(s"Pause sync until round starts ${rt.round}") + api.update(rt.round)(_.withSync(_.pause)).void // no writing the relay; only reading! // this can take a long time if the source is slow @@ -84,17 +87,17 @@ final private class RelayFetch( .map(games => rt.tour.players.fold(games)(_.update(games))) .flatMap(fidePlayers.enrichGames(rt.tour)) .map(games => rt.tour.teams.fold(games)(_.update(games))) - .mon(_.relay.fetchTime(rt.tour.official, rt.round.slug)) - .addEffect(gs => lila.mon.relay.games(rt.tour.official, rt.round.slug).update(gs.size)) + .mon(_.relay.fetchTime(rt.tour.official, rt.tour.id, rt.tour.slug)) + .addEffect(gs => lila.mon.relay.games(rt.tour.official, rt.tour.id, rt.round.slug).update(gs.size)) .flatMap: games => sync .updateStudyChapters(rt, games) .withTimeoutError(7 seconds, SyncResult.Timeout) - .mon(_.relay.syncTime(rt.tour.official, rt.round.slug)) + .mon(_.relay.syncTime(rt.tour.official, rt.tour.id, rt.tour.slug)) .map: res => res -> updating: _.withSync(_.addLog(SyncLog.event(res.nbMoves, none))) - .copy(finished = games.nonEmpty && games.forall(_.ending.isDefined)) + .copy(finished = games.nonEmpty && games.forall(_.outcome.isDefined)) .recover: case e: Exception => val result = e.match @@ -119,11 +122,14 @@ final private class RelayFetch( ): Updating[RelayRound] = val round = updating.current result match - case result: SyncResult.Ok if result.nbMoves > 0 => - lila.mon.relay.moves(tour.official, round.slug).increment(result.nbMoves) - if !round.hasStarted && !tour.official then - irc.broadcastStart(round.id, round.withTour(tour).fullName) - continueRelay(tour, updating(_.ensureStarted.resume(tour.official))) + case result: SyncResult.Ok if result.hasMovesOrTags => + api.syncTargetsOfSource(round) + if result.nbMoves > 0 then + lila.mon.relay.moves(tour.official, tour.id, tour.slug).increment(result.nbMoves) + if !round.hasStarted && !tour.official then + irc.broadcastStart(round.id, round.withTour(tour).fullName) + continueRelay(tour, updating(_.ensureStarted.resume(tour.official))) + else continueRelay(tour, updating) case _ => continueRelay(tour, updating) private def continueRelay(tour: RelayTour, updating: Updating[RelayRound]): Updating[RelayRound] = @@ -139,10 +145,7 @@ final private class RelayFetch( .filterNot(_.contains("Found an empty PGN")) .foreach { irc.broadcastError(round.id, round.withTour(tour).fullName, _) } Seconds(60) - else - round.sync.period | Seconds: - if upstream.isLcc && !tour.official then 12 - else 5 + else round.sync.period | dynamicPeriod(tour, round, upstream) updating: _.withSync: _.copy( @@ -153,6 +156,21 @@ final private class RelayFetch( } some ) + private def dynamicPeriod(tour: RelayTour, round: RelayRound, upstream: Sync.Upstream) = Seconds: + val base = + if upstream.hasLcc then 6 + else if upstream.isRound then 10 // uses push so no need to pull oftenrelayfetch + else 2 + base * { + if tour.tier.exists(_ > RelayTour.Tier.NORMAL) then 1 + else if tour.official then 2 + else 3 + } * { + if upstream.hasLcc && round.crowd.exists(_ < 10) then 2 else 1 + } * { + if round.hasStarted then 1 else 2 + } + private val gameIdsUpstreamPgnFlags = PgnDump.WithFlags( clocks = true, moves = true, @@ -167,22 +185,21 @@ final private class RelayFetch( private def fetchGames(rt: RelayRound.WithTour): Fu[RelayGames] = given CanProxy = CanProxy(rt.tour.official) rt.round.sync.upstream.so: - case Sync.UpstreamIds(ids) => fetchFromGameIds(rt.tour, ids) - case lcc: Sync.UpstreamLcc => fetchFromUpstream(lcc, RelayFetch.maxChapters) - case url: Sync.UpstreamUrl => delayer(url, rt.round, fetchFromUpstream) - case Sync.UpstreamUrls(urls) => - urls - .traverse: url => - delayer(url, rt.round, fetchFromUpstream(using CanProxy(rt.tour.official))) - .map(_.flatten.toVector) + case Sync.Upstream.Ids(ids) => fetchFromGameIds(rt.tour, ids) + case Sync.Upstream.Url(url) => delayer(url, rt.round, fetchFromUpstream(rt)) + case Sync.Upstream.Urls(urls) => + urls.toVector + .parallel: url => + delayer(url, rt.round, fetchFromUpstreamWithRecover(rt)) + .map(_.flatten) private def fetchFromGameIds(tour: RelayTour, ids: List[GameId]): Fu[RelayGames] = gameRepo .gamesFromSecondary(ids) .flatMap(gameProxy.upgradeIfPresent) .flatMap(gameRepo.withInitialFens) - .flatMap { games => - if games.size == ids.size then + .flatMap: games => + if games.sizeIs == ids.size then val pgnFlags = gameIdsUpstreamPgnFlags.copy(delayMoves = !tour.official) given play.api.i18n.Lang = lila.core.i18n.defaultLang games @@ -192,52 +209,93 @@ final private class RelayFetch( else throw LilaInvalid: s"Invalid game IDs: ${ids.filter(id => !games.exists(_._1.id == id)).mkString(", ")}" - } - .flatMap(multiPgnToGames(_).toFuture) + .flatMap(multiPgnToGames.future) - private def fetchFromUpstream(using - canProxy: CanProxy - )(upstream: Sync.FetchableUpstream, max: Max): Fu[RelayGames] = + private object lccCache: + import DgtJson.GameJson + type LccGameKey = String + // cache finished games so they're not requested again for a while + private val finishedGames = + cacheApi.notLoadingSync[LccGameKey, GameJson](512, "relay.fetch.finishedLccGames"): + _.expireAfterWrite(8 minutes).build() + // cache created (non-started) games until they start + private val createdGames = + cacheApi.notLoadingSync[LccGameKey, GameJson](256, "relay.fetch.createdLccGames"): + _.expireAfter[LccGameKey, GameJson]( + create = (key, _) => (if key.startsWith("started ") then 1 minute else 5 minutes), + update = (_, _, current) => current, + read = (_, _, current) => current + ).build() + // cache games with number > 12 to reduce load on big tournaments + val tailAt = 12 + private val tailGames = + cacheApi.notLoadingSync[LccGameKey, GameJson](256, "relay.fetch.tailLccGames"): + _.expireAfterWrite(1 minutes).build() + + def apply(lcc: RelayRound.Sync.Lcc, index: Int, roundTags: Tags, started: Boolean)( + fetch: () => Fu[GameJson] + ): Fu[GameJson] = + val key = s"${started.so("started ")}${lcc.id} ${lcc.round} $index" + finishedGames + .getIfPresent(key) + .orElse(createdGames.getIfPresent(key)) + .orElse((index >= lccCache.tailAt).so(tailGames.getIfPresent(key))) + .match + case Some(game) => fuccess(game) + case None => + fetch().addEffect: game => + if game.moves.isEmpty then createdGames.put(key, game) + else if game.mergeRoundTags(roundTags).outcome.isDefined then finishedGames.put(key, game) + else if index >= lccCache.tailAt then tailGames.put(key, game) + + private def fetchFromUpstreamWithRecover(rt: RelayRound.WithTour)(url: URL)(using + CanProxy + ): Fu[RelayGames] = + fetchFromUpstream(rt)(url).recover: + case e: Exception => + logger.info(s"Fetch error in multi-url ${rt.round.id} $url ${e.getMessage.take(80)}", e) + Vector.empty + + private def fetchFromUpstream(rt: RelayRound.WithTour)(url: URL)(using CanProxy): Fu[RelayGames] = import DgtJson.* formatApi - .get(upstream) + .get(url) .flatMap { - case RelayFormat.SingleFile(doc) => - doc.format match - // all games in a single PGN file - case RelayFormat.DocFormat.Pgn => httpGetPgn(doc.url).map { MultiPgn.split(_, max) } - // maybe a single JSON game? Why not - case RelayFormat.DocFormat.Json => - httpGetJson[GameJson](doc.url).map: game => - MultiPgn(List(game.toPgn())) - case RelayFormat.ManyFiles(indexUrl, makeGameDoc) => - httpGetJson[RoundJson](indexUrl).flatMap: round => - round.pairings.zipWithIndex - .map: (pairing, i) => - val number = i + 1 - val gameDoc = makeGameDoc(number) - gameDoc.format - .match - case RelayFormat.DocFormat.Pgn => httpGetPgn(gameDoc.url) - case RelayFormat.DocFormat.Json => - httpGetJson[GameJson](gameDoc.url) - .recover: - case _: Exception => GameJson(moves = Nil, result = none) - .map { _.toPgn(pairing.tags) } + case RelayFormat.Round(id) => + studyChapterRepo + .orderedByStudyLoadingAllInMemory(id.into(StudyId)) + .map(_.view.map(RelayGame.fromChapter).toVector) + case RelayFormat.SingleFile(url) => + httpGetPgn(url).map { MultiPgn.split(_, RelayFetch.maxChapters) }.flatMap(multiPgnToGames.future) + case RelayFormat.LccWithGames(lcc) => + httpGetJson[RoundJson](lcc.indexUrl).flatMap: round => + val lookForStart: Boolean = + rt.round.startsAt + .map(_.minusSeconds(rt.round.sync.delay.so(_.value) + 5 * 60)) + .forall(_.isBeforeNow) + round.pairings + .mapWithIndex: (pairing, i) => + val game = i + 1 + val tags = pairing.tags(lcc.round, game) + lccCache(lcc, game, tags, lookForStart): () => + httpGetJson[GameJson](lcc.gameUrl(game)).recover: + case _: Exception => GameJson(moves = Nil, result = none) + .map { _.toPgn(tags) } .recover: _ => - PgnStr(s"${pairing.tags}\n\n${pairing.result}") - .map(number -> _) + PgnStr(s"${tags}\n\n${pairing.result}") + .map(game -> _) .parallel - .map: results => - MultiPgn(results.sortBy(_._1).map(_._2)) - case RelayFormat.ManyFilesLater(indexUrl) => - httpGetJson[RoundJson](indexUrl).map: round => - MultiPgn: - round.pairings.map: pairing => - PgnStr(s"${pairing.tags}\n\n${pairing.result}") - + .map: pgns => + MultiPgn(pgns.sortBy(_._1).map(_._2)) + .flatMap(multiPgnToGames.future) + case RelayFormat.LccWithoutGames(lcc) => + httpGetJson[RoundJson](lcc.indexUrl) + .map: round => + MultiPgn: + round.pairings.mapWithIndex: (pairing, i) => + PgnStr(s"${pairing.tags(lcc.round, i + 1)}\n\n${pairing.result}") + .flatMap(multiPgnToGames.future) } - .flatMap { multiPgnToGames(_).toFuture } private def httpGetPgn(url: URL)(using CanProxy): Fu[PgnStr] = PgnStr.from(formatApi.httpGetAndGuessCharset(url)) @@ -269,7 +327,7 @@ private object RelayFetch: result: Option[String] ): import chess.format.pgn.* - def tags = Tags: + def tags(round: Int, game: Int) = Tags: List( white.flatMap(_.fullName).map { Tag(_.White, _) }, white.flatMap(_.title).map { Tag(_.WhiteTitle, _) }, @@ -277,22 +335,27 @@ private object RelayFetch: black.flatMap(_.fullName).map { Tag(_.Black, _) }, black.flatMap(_.title).map { Tag(_.BlackTitle, _) }, black.flatMap(_.fideid).map { Tag(_.BlackFideId, _) }, - result.map(Tag(_.Result, _)) + result.map(Tag(_.Result, _)), + Tag(_.Round, s"$round.$game").some ).flatten - case class RoundJson(pairings: List[RoundJsonPairing]) + case class RoundJson(pairings: List[RoundJsonPairing]): + def finishedGameIndexes: List[Int] = pairings.zipWithIndex.collect: + case (pairing, i) if pairing.result.forall(_ != "*") => i given Reads[PairingPlayer] = Json.reads given Reads[RoundJsonPairing] = Json.reads given Reads[RoundJson] = Json.reads case class GameJson(moves: List[String], result: Option[String], chess960: Option[Int] = none): def outcome = result.flatMap(Outcome.fromResult) - def toPgn(extraTags: Tags = Tags.empty): PgnStr = + def mergeRoundTags(roundTags: Tags): Tags = val fenTag = chess960 .filter(_ != 518) // LCC sends 518 for standard chess .flatMap(chess.variant.Chess960.positionToFen) .map(pos => Tag(_.FEN, pos.value)) val outcomeTag = outcome.map(o => Tag(_.Result, Outcome.showResult(o.some))) - val tags = extraTags ++ Tags(List(fenTag, outcomeTag).flatten) + roundTags ++ Tags(List(fenTag, outcomeTag).flatten) + def toPgn(roundTags: Tags): PgnStr = + val mergedTags = mergeRoundTags(roundTags) val strMoves = moves .map(_.split(' ')) .mapWithIndex: (move, index) => @@ -304,7 +367,7 @@ private object RelayFetch: ) .render .mkString(" ") - PgnStr(s"$tags\n\n$strMoves") + PgnStr(s"$mergedTags\n\n$strMoves") given Reads[GameJson] = Json.reads object multiPgnToGames: @@ -320,11 +383,13 @@ private object RelayFetch: else (acc :+ game, index + 1).asRight[LilaInvalid] .map(_._1) + def future(multiPgn: MultiPgn): Fu[Vector[RelayGame]] = apply(multiPgn).toFuture + private val pgnCache: LoadingCache[PgnStr, Either[LilaInvalid, RelayGame]] = CacheApi .scaffeineNoScheduler(using scala.concurrent.ExecutionContextOpportunistic) .expireAfterAccess(2 minutes) - .maximumSize(512) + .maximumSize(1024) .build(compute) private def compute(pgn: PgnStr): Either[LilaInvalid, RelayGame] = @@ -342,5 +407,5 @@ private object RelayFetch: comments = Comments.empty, children = res.root.children.updateMainline(_.copy(comments = Comments.empty)) ), - ending = res.end + outcome = res.end.map(_.outcome) ) diff --git a/modules/relay/src/main/RelayFidePlayerApi.scala b/modules/relay/src/main/RelayFidePlayerApi.scala index 97cf47af830a1..795182e43c12c 100644 --- a/modules/relay/src/main/RelayFidePlayerApi.scala +++ b/modules/relay/src/main/RelayFidePlayerApi.scala @@ -24,9 +24,7 @@ final private class RelayFidePlayerApi(guessPlayer: lila.core.fide.GuessPlayer)( update(tags, tc, _) private def guessTimeControl(tour: RelayTour): Option[FideTC] = - tour.description - .split('|') - .lift(2) + tour.info.tc .map(_.trim.toLowerCase.replace("classical", "standard")) .so: tcStr => FideTC.values.find(tc => tcStr.contains(tc.toString)) diff --git a/modules/relay/src/main/RelayFormat.scala b/modules/relay/src/main/RelayFormat.scala index 0270c7a0d8436..99717ca87af0d 100644 --- a/modules/relay/src/main/RelayFormat.scala +++ b/modules/relay/src/main/RelayFormat.scala @@ -21,6 +21,7 @@ import lila.memo.{ CacheApi, SettingStore } import lila.study.MultiPgn final private class RelayFormatApi( + roundRepo: RelayRoundRepo, ws: StandaloneWSClient, cacheApi: CacheApi, proxyCredentials: SettingStore[Option[Credentials]] @@ ProxyCredentials, @@ -29,68 +30,46 @@ final private class RelayFormatApi( )(using Executor): import RelayFormat.* - import RelayRound.Sync.{ FetchableUpstream, UpstreamUrl, UpstreamLcc } - private val cache = cacheApi[(FetchableUpstream, CanProxy), RelayFormat](64, "relay.format"): + private val cache = cacheApi[(URL, CanProxy), RelayFormat](64, "relay.format"): _.expireAfterWrite(5 minutes) .buildAsyncFuture: (url, proxy) => guessFormat(url)(using proxy) - def get(upstream: FetchableUpstream)(using proxy: CanProxy): Fu[RelayFormat] = - cache.get(upstream -> proxy) + def get(url: URL)(using proxy: CanProxy): Fu[RelayFormat] = + cache.get(url -> proxy) - def refresh(upstream: FetchableUpstream): Unit = + def refresh(url: URL): Unit = CanProxy .from(List(false, true)) .foreach: proxy => - cache.invalidate(upstream -> proxy) - - private def guessFormat(upstream: FetchableUpstream)(using CanProxy): Fu[RelayFormat] = { - - def parsedUrl = URL.parse(upstream.fetchUrl) - - def guessLcc: Fu[Option[RelayFormat]] = upstream.isLcc.so(guessManyFiles(parsedUrl)) - - def guessSingleFile(url: URL): Fu[Option[RelayFormat]] = - List( - url.some, - (!url.pathSegments.contains(mostCommonSingleFileName)).option( - addPart(url, mostCommonSingleFileName) - ) - ).flatten.distinct - .findM(looksLikePgn) - .dmap2: (u: URL) => - SingleFile(pgnDoc(u)) - - def guessManyFiles(url: URL): Fu[Option[RelayFormat]] = - (List(url) ::: mostCommonIndexNames - .filterNot(url.pathSegments.contains) - .map(addPart(url, _))) - .findM(looksLikeJson) - .flatMapz: index => - val jsonUrl = (n: Int) => jsonDoc(replaceLastPart(index, s"game-$n.json")) - val pgnUrl = (n: Int) => pgnDoc(replaceLastPart(index, s"game-$n.pgn")) - looksLikeJson(jsonUrl(1).url) - .recover: - case NotFound(_) => false - .map(_.option(jsonUrl)) - .orElse: - looksLikePgn(pgnUrl(1).url) - .recover: - case NotFound(_) => false - .map(_.option(pgnUrl)) - .dmap2: - ManyFiles(index, _) - .dmap(_.orElse(ManyFilesLater(index).some)) - - guessLcc - .orElse(guessSingleFile(parsedUrl)) - .orElse(guessManyFiles(parsedUrl)) - .orFailWith(LilaInvalid(s"No games found at $upstream")) - - }.addEffect { format => - logger.info(s"guessed format of $upstream: $format") - } + cache.invalidate(url -> proxy) + + private def guessFormat(url: URL)(using CanProxy): Fu[RelayFormat] = + RelayRound.Sync.Upstream + .Url(url) + .lcc + .match + case Some(lcc) => + looksLikeJson(lcc.indexUrl).flatMapz: + looksLikeJson(lcc.gameUrl(1)) + .recoverDefault(false)(_ => ()) + .map: + if _ then LccWithGames(lcc).some + else LccWithoutGames(lcc).some + case None => + guessRelayRound(url).orElse: + looksLikePgn(url).mapz(SingleFile(url).some) + .orFailWith(LilaInvalid(s"No games found at $url")) + .addEffect: format => + logger.info(s"guessed format of $url: $format") + + private def guessRelayRound(url: URL): Fu[Option[RelayFormat.Round]] = + RelayRound.Sync.Upstream + .Url(url) + .roundId + .so: id => + roundRepo.exists(id).map(_.option(RelayFormat.Round(id))) private[relay] def httpGet(url: URL)(using CanProxy): Fu[String] = httpGetResponse(url).map(_.body) @@ -115,7 +94,7 @@ final private class RelayFormatApi( .get() .flatMap: res => if res.status == 200 then fuccess(res) - else if res.status == 404 then fufail(NotFound(url.toString)) + else if res.status == 404 then fufail(NotFound(url)) else fufail(s"[${res.status}] $url") .monSuccess(_.relay.httpGet(url.host.toString, proxy)) @@ -151,37 +130,18 @@ final private class RelayFormatApi( catch case _: Exception => false private def looksLikeJson(url: URL)(using CanProxy): Fu[Boolean] = httpGet(url).map(looksLikeJson) -sealed private trait RelayFormat +private enum RelayFormat: + case Round(id: RelayRoundId) + case SingleFile(url: URL) + case LccWithGames(lcc: RelayRound.Sync.Lcc) + // there will be game files with names like "game-1.json" or "game-1.pgn" + // but not at the moment. The index is still useful. + case LccWithoutGames(lcc: RelayRound.Sync.Lcc) private object RelayFormat: opaque type CanProxy = Boolean object CanProxy extends YesNo[CanProxy] - enum DocFormat: - case Json, Pgn - - case class RemoteDoc(url: URL, format: DocFormat) - - def jsonDoc(url: URL) = RemoteDoc(url, DocFormat.Json) - def pgnDoc(url: URL) = RemoteDoc(url, DocFormat.Pgn) - - case class SingleFile(doc: RemoteDoc) extends RelayFormat - - type GameNumberToDoc = Int => RemoteDoc - - case class ManyFiles(jsonIndex: URL, game: GameNumberToDoc) extends RelayFormat: - override def toString = s"Manyfiles($jsonIndex, ${game(0)})" - - // there will be game files with names like "game-1.json" or "game-1.pgn" - // but not at the moment. The index is still useful. - case class ManyFilesLater(jsonIndex: URL) extends RelayFormat: - override def toString = s"ManyfilesLater($jsonIndex)" - - def addPart(url: URL, part: String) = url.withPath(s"${url.path}/$part") - def replaceLastPart(url: URL, withPart: String) = url.withPath(s"${url.path}/../$withPart") - - val mostCommonSingleFileName = "games.pgn" - val mostCommonIndexNames = List("round.json", "index.json") - - case class NotFound(message: String) extends LilaException + case class NotFound(url: URL) extends LilaException: + override val message = s"404: $url" diff --git a/modules/relay/src/main/RelayGame.scala b/modules/relay/src/main/RelayGame.scala index dee3cc97ad08f..0a033b8e44edf 100644 --- a/modules/relay/src/main/RelayGame.scala +++ b/modules/relay/src/main/RelayGame.scala @@ -4,12 +4,13 @@ import chess.format.pgn.{ Tag, TagType, Tags } import lila.study.{ MultiPgn, StudyPgnImport, PgnDump } import lila.tree.Root +import chess.Outcome case class RelayGame( tags: Tags, variant: chess.variant.Variant, root: Root, - ending: Option[StudyPgnImport.End] + outcome: Option[Outcome] ): // We don't use tags.boardNumber. @@ -31,8 +32,8 @@ case class RelayGame( def resetToSetup = copy( root = root.withoutChildren, - ending = None, - tags = tags.copy(value = tags.value.filter(_.name != Tag.Result)) + tags = tags.copy(value = tags.value.filter(_.name != Tag.Result)), + outcome = None ) def fideIdsPair: Option[PairOf[Option[chess.FideId]]] = @@ -42,6 +43,8 @@ case class RelayGame( List(RelayGame.whiteTags, RelayGame.blackTags).exists: _.forall(tag => tags(tag).isEmpty) + def showResult = Outcome.showResult(outcome) + private object RelayGame: val lichessDomains = List("lichess.org", "lichess.dev") @@ -53,6 +56,13 @@ private object RelayGame: val whiteTags: TagNames = List(_.White, _.WhiteFideId) val blackTags: TagNames = List(_.Black, _.BlackFideId) + def fromChapter(c: lila.study.Chapter) = RelayGame( + tags = c.tags, + variant = c.setup.variant, + root = c.root, + outcome = c.tags.outcome + ) + import scalalib.Iso import chess.format.pgn.{ InitialComments, Pgn } val iso: Iso[RelayGames, MultiPgn] = @@ -62,7 +72,8 @@ private object RelayGame: variations = false, clocks = true, source = true, - orientation = false + orientation = false, + site = none ) Iso[RelayGames, MultiPgn]( gs => diff --git a/modules/relay/src/main/RelayListing.scala b/modules/relay/src/main/RelayListing.scala index 49191e9c3f330..dfceff7f8ec77 100644 --- a/modules/relay/src/main/RelayListing.scala +++ b/modules/relay/src/main/RelayListing.scala @@ -26,10 +26,9 @@ final class RelayListing( local = "_id", foreign = "tourId", pipe = List( - $doc("$match" -> $doc("finished" -> false)), - $doc("$addFields" -> $doc("sync.log" -> $arr())), - $doc("$sort" -> RelayRoundRepo.sort.chrono), - $doc("$limit" -> 1) + $doc("$match" -> $doc("finished" -> false)), + $doc("$sort" -> RelayRoundRepo.sort.chrono), + $doc("$limit" -> 1) ) ) for diff --git a/modules/relay/src/main/RelayPgnStream.scala b/modules/relay/src/main/RelayPgnStream.scala index 895fa2a2fef77..84d06cbdd9d40 100644 --- a/modules/relay/src/main/RelayPgnStream.scala +++ b/modules/relay/src/main/RelayPgnStream.scala @@ -27,7 +27,8 @@ final class RelayPgnStream( variations = false, clocks = true, source = false, - orientation = false + orientation = false, + site = none ) private val fileR = """[\s,]""".r private val dateFormatter = java.time.format.DateTimeFormatter.ofPattern("yyyy.MM.dd") diff --git a/modules/relay/src/main/RelayPush.scala b/modules/relay/src/main/RelayPush.scala index 96b45d85c5b75..bdcdb12df6f74 100644 --- a/modules/relay/src/main/RelayPush.scala +++ b/modules/relay/src/main/RelayPush.scala @@ -7,17 +7,19 @@ import chess.{ ErrorStr, Game, Replay, Square } import scala.concurrent.duration.* import scalalib.actor.AsyncActorSequencers -import lila.study.{ MultiPgn, StudyPgnImport } +import lila.study.{ MultiPgn, StudyPgnImport, ChapterPreviewApi } final class RelayPush( sync: RelaySync, api: RelayApi, stats: RelayStatsApi, + chapterPreview: ChapterPreviewApi, + fidePlayers: RelayFidePlayerApi, irc: lila.core.irc.IrcApi )(using ActorSystem, Executor, Scheduler): private val workQueue = AsyncActorSequencers[RelayRoundId]( - maxSize = Max(8), + maxSize = Max(32), expiration = 1 minute, timeout = 10 seconds, name = "relay.push", @@ -31,31 +33,40 @@ final class RelayPush( if rt.round.sync.hasUpstream then fuccess(List(Left(Failure(Tags.empty, "The relay has an upstream URL, and cannot be pushed to.")))) else - val parsed = pgnToGames(pgn) - val games = parsed.collect { case Right(g) => g }.toVector - val response = parsed.map(_.map(g => Success(g.tags, g.root.mainline.size))) + val parsed = pgnToGames(pgn) + val games = parsed.collect { case Right(g) => g }.toVector + val response: List[Either[Failure, Success]] = + parsed.map(_.map(g => Success(g.tags, g.root.mainline.size))) + val andSyncTargets = response.exists(_.isRight) - rt.round.sync.nonEmptyDelay.fold(push(rt, games).inject(response)): delay => - after(delay.value.seconds)(push(rt, games)) - fuccess(response) + rt.round.sync.nonEmptyDelay match + case None => push(rt, games, andSyncTargets).inject(response) + case Some(delay) => + after(delay.value.seconds)(push(rt, games, andSyncTargets)) + fuccess(response) - private def push(rt: RelayRound.WithTour, games: Vector[RelayGame]) = + private def push(rt: RelayRound.WithTour, rawGames: Vector[RelayGame], andSyncTargets: Boolean) = workQueue(rt.round.id): - sync - .updateStudyChapters(rt, rt.tour.players.fold(games)(_.update(games))) - .map: res => - SyncLog.event(res.nbMoves, none) - .recover: - case e: Exception => SyncLog.event(0, e.some) - .flatMap: event => - if !rt.round.hasStarted && !rt.tour.official && event.hasMoves then - irc.broadcastStart(rt.round.id, rt.fullName) - stats.setActive(rt.round.id) - api - .update(rt.round): r1 => - val r2 = r1.withSync(_.addLog(event)) - val r3 = if event.hasMoves then r2.ensureStarted.resume(rt.tour.official) else r2 - r3.copy(finished = games.nonEmpty && games.forall(_.ending.isDefined)) + for + withPlayers <- fuccess(rt.tour.players.fold(rawGames)(_.update(rawGames))) + games <- fidePlayers.enrichGames(rt.tour)(withPlayers) + event <- sync + .updateStudyChapters(rt, rt.tour.players.fold(games)(_.update(games))) + .map: res => + SyncLog.event(res.nbMoves, none) + .recover: + case e: Exception => SyncLog.event(0, e.some) + _ = if !rt.round.hasStarted && !rt.tour.official && event.hasMoves then + irc.broadcastStart(rt.round.id, rt.fullName) + _ = stats.setActive(rt.round.id) + allGamesFinished <- (games.nonEmpty && games.forall(_.outcome.isDefined)).so: + chapterPreview.dataList(rt.round.studyId).map(_.forall(_.finished)) + round <- api.update(rt.round): r1 => + val r2 = r1.withSync(_.addLog(event)) + val r3 = if event.hasMoves then r2.ensureStarted.resume(rt.tour.official) else r2 + r3.copy(finished = allGamesFinished) + _ <- andSyncTargets.so(api.syncTargetsOfSource(round)) + yield () private def pgnToGames(pgnBody: PgnStr): List[Either[Failure, RelayGame]] = MultiPgn @@ -75,7 +86,7 @@ final class RelayPush( children = game.root.children .updateMainline(_.copy(comments = lila.tree.Node.Comments.empty)) ), - ending = game.end + outcome = game.end.map(_.outcome) ) ) diff --git a/modules/relay/src/main/RelayRound.scala b/modules/relay/src/main/RelayRound.scala index 6308fe4d0f26c..c655e1ae1798d 100644 --- a/modules/relay/src/main/RelayRound.scala +++ b/modules/relay/src/main/RelayRound.scala @@ -5,6 +5,7 @@ import reactivemongo.api.bson.Macros.Annotations.Key import scalalib.model.Seconds import lila.study.Study +import io.mola.galimatias.URL case class RelayRound( /* Same as the Study id it refers to */ @@ -51,8 +52,6 @@ case class RelayRound( case Some(at) => at.isBefore(nowInstant.minusHours(3)) case None => createdAt.isBefore(nowInstant.minusDays(1)) - def stateHash = (hasStarted, finished) - def withSync(f: RelayRound.Sync => RelayRound.Sync) = copy(sync = f(sync)) def withTour(tour: RelayTour) = RelayRound.WithTour(this, tour) @@ -80,12 +79,13 @@ object RelayRound: log: SyncLog ): def hasUpstream = upstream.isDefined + def isPush = upstream.isEmpty def renew(official: Boolean) = if hasUpstream then copy(until = nowInstant.plusHours(if official then 3 else 1).some) else pause - def ongoing = until.so(nowInstant.isBefore) + def ongoing = until.so(_.isAfterNow) def play(official: Boolean) = if hasUpstream then renew(official).copy(nextAt = nextAt.orElse(nowInstant.plusSeconds(3).some)) @@ -114,30 +114,46 @@ object RelayRound: override def toString = upstream.toString object Sync: - sealed trait Upstream: - def isLcc = false - sealed trait FetchableUpstream extends Upstream: - def fetchUrl: String - def formUrl: String - case class UpstreamUrl(url: String) extends FetchableUpstream: - def fetchUrl = url - def formUrl = url - case class UpstreamUrls(urls: List[FetchableUpstream]) extends Upstream - case class UpstreamIds(ids: List[GameId]) extends Upstream - case class UpstreamLcc(lcc: String, round: Int) extends FetchableUpstream: - override def isLcc = true - def id = lcc - def fetchUrl = s"http://1.pool.livechesscloud.com/get/$id/round-$round/index.json" - def viewUrl = s"https://view.livechesscloud.com/#$id" - def formUrl = s"$viewUrl $round" - object UpstreamLcc: - private val idRegex = """.*view\.livechesscloud\.com/?#?([0-9a-f\-]+)""".r - def findId(url: UpstreamUrl): Option[String] = url.url match - case idRegex(id) => id.some - case _ => none - def find(url: String): Option[UpstreamLcc] = url.split(' ').map(_.trim).filter(_.nonEmpty) match - case Array(idRegex(id), round) => round.toIntOption.map(UpstreamLcc(id, _)) - case _ => none + enum Upstream: + case Url(url: URL) extends Upstream + case Urls(urls: List[URL]) extends Upstream + case Ids(ids: List[GameId]) extends Upstream + def isUrl = this match + case Url(_) => true + case _ => false + def lcc: Option[Lcc] = this match + case Url(url) => + url.toString match + case lccRegex(id, round) => round.toIntOption.map(Lcc(id, _)) + case _ => none + case _ => none + def hasLcc = this match + case Url(url) => Sync.looksLikeLcc(url) + case Urls(urls) => urls.exists(Sync.looksLikeLcc) + case _ => false + + def roundId: Option[RelayRoundId] = this match + case Url(url) => + url.path.split("/") match + case Array("", "broadcast", _, _, id) => + val cleanId = if id.endsWith(".pgn") then id.dropRight(4) else id + (cleanId.size == 8).option(RelayRoundId(cleanId)) + case _ => none + case _ => none + def isRound = roundId.isDefined + def roundIds: List[RelayRoundId] = this match + case url: Url => url.roundId.toList + case Urls(urls) => urls.map(Url.apply).flatMap(_.roundId) + case _ => Nil + + case class Lcc(id: String, round: Int): + def pageUrl = URL.parse(s"https://view.livechesscloud.com/#$id/$round") + def indexUrl = URL.parse(s"http://1.pool.livechesscloud.com/get/$id/round-$round/index.json") + def gameUrl(game: Int) = + URL.parse(s"http://1.pool.livechesscloud.com/get/$id/round-$round/game-$game.json") + + private val lccRegex = """view\.livechesscloud\.com/?#?([0-9a-f\-]+)/(\d+)""".r.unanchored + private def looksLikeLcc(url: URL) = url.toString.contains(".livechesscloud.com/") trait AndTour: val tour: RelayTour diff --git a/modules/relay/src/main/RelayRoundForm.scala b/modules/relay/src/main/RelayRoundForm.scala index ec0a821b282e2..baf87aa94d621 100644 --- a/modules/relay/src/main/RelayRoundForm.scala +++ b/modules/relay/src/main/RelayRoundForm.scala @@ -12,24 +12,25 @@ import lila.common.Form.{ cleanText, into, stringIn, formatter } import lila.core.perm.Granter import lila.relay.RelayRound.Sync -import lila.relay.RelayRound.Sync.UpstreamUrl +import lila.relay.RelayRound.Sync.Upstream final class RelayRoundForm(using mode: Mode): import RelayRoundForm.* import lila.common.Form.ISOInstantOrTimestamp - private given Formatter[Sync.UpstreamUrl] = formatter.stringTryFormatter(validateUpstreamUrl, _.fetchUrl) - private given Formatter[Sync.UpstreamUrls] = formatter.stringTryFormatter( + private given Formatter[Upstream.Url] = + formatter.stringTryFormatter(str => validateUpstreamUrl(str).map(Upstream.Url.apply), _.url.toString) + private given Formatter[Upstream.Urls] = formatter.stringTryFormatter( _.linesIterator.toList .map(_.trim) .filter(_.nonEmpty) - .traverse(validateUpstreamUrlOrLcc) + .traverse(validateUpstreamUrl) .map(_.distinct) - .map(Sync.UpstreamUrls.apply), - _.urls.map(_.formUrl).mkString("\n") + .map(Upstream.Urls.apply), + _.urls.mkString("\n") ) - private given Formatter[Sync.UpstreamIds] = formatter.stringTryFormatter( + private given Formatter[Upstream.Ids] = formatter.stringTryFormatter( _.split(' ').toList .map(_.trim) .traverse: i => @@ -38,35 +39,39 @@ final class RelayRoundForm(using mode: Mode): .map(_.mkString(", ")) .filterOrElse(_.sizeIs <= RelayFetch.maxChapters.value, s"Max games: ${RelayFetch.maxChapters}") .map(_.distinct) - .map(Sync.UpstreamIds.apply), + .map(Upstream.Ids.apply), _.ids.mkString(" ") ) - val lccMapping = mapping( - "id" -> cleanText(minLength = 10, maxLength = 40), - "round" -> number(min = 1, max = 999) - )(Sync.UpstreamLcc.apply)(unapply) + private def lccIsComplete(url: Upstream.Url) = + url.lcc.isDefined || !url.url.host.toString.endsWith("livechesscloud.com") - val roundMapping = + def roundMapping(using Me) = mapping( "name" -> cleanText(minLength = 3, maxLength = 80).into[RelayRound.Name], "caption" -> optional(cleanText(minLength = 3, maxLength = 80).into[RelayRound.Caption]), "syncSource" -> optional(stringIn(sourceTypes.map(_._1).toSet)), - "syncUrl" -> optional(of[Sync.UpstreamUrl]), - "syncUrls" -> optional(of[Sync.UpstreamUrls]), - "syncLcc" -> optional(lccMapping), - "syncIds" -> optional(of[Sync.UpstreamIds]), - "startsAt" -> optional(ISOInstantOrTimestamp.mapping), - "finished" -> optional(boolean), - "period" -> optional(number(min = 2, max = 60).into[Seconds]), - "delay" -> optional(number(min = 0, max = RelayDelay.maxSeconds.value).into[Seconds]), - "onlyRound" -> optional(number(min = 1, max = 999)), + "syncUrl" -> optional( + of[Upstream.Url] + .verifying("LCC URLs must end with /{round-number}, e.g. /5 for round 5", lccIsComplete) + .verifying( + "Invalid source URL", + u => !u.url.host.toString.endsWith("lichess.org") || Granter(_.Relay) + ) + ), + "syncUrls" -> optional(of[Upstream.Urls]), + "syncIds" -> optional(of[Upstream.Ids]), + "startsAt" -> optional(ISOInstantOrTimestamp.mapping), + "finished" -> optional(boolean), + "period" -> optional(number(min = 2, max = 60).into[Seconds]), + "delay" -> optional(number(min = 0, max = RelayDelay.maxSeconds.value).into[Seconds]), + "onlyRound" -> optional(number(min = 1, max = 999)), "slices" -> optional: nonEmptyText .transform[List[RelayGame.Slice]](RelayGame.Slices.parse, RelayGame.Slices.show) )(Data.apply)(unapply) - def create(trs: RelayTour.WithRounds) = Form( + def create(trs: RelayTour.WithRounds)(using Me) = Form( roundMapping .verifying( s"Maximum rounds per tournament: ${RelayTour.maxRelays}", @@ -74,16 +79,21 @@ final class RelayRoundForm(using mode: Mode): ) ).fill(fillFromPrevRounds(trs.rounds)) - def edit(r: RelayRound) = Form(roundMapping).fill(Data.make(r)) + def edit(r: RelayRound)(using Me) = Form( + roundMapping + .verifying( + "The round source cannot be itself", + d => d.syncSource.forall(_ != "url") || d.syncUrl.forall(_.roundId.forall(_ != r.id)) + ) + ).fill(Data.make(r)) object RelayRoundForm: val sourceTypes = List( + "push" -> "Broadcaster App", "url" -> "Single PGN URL", "urls" -> "Combine several PGN URLs", - "lcc" -> "LiveChessCloud page", - "ids" -> "Lichess game IDs", - "push" -> "Push local games" + "ids" -> "Lichess game IDs" ) private val roundNumberRegex = """([^\d]*)(\d{1,2})([^\d]*)""".r @@ -108,22 +118,23 @@ object RelayRoundForm: roundNumberIn(old.name.value).contains(n - 1) p <- prev yield replaceRoundNumber(p.name.value, nextNumber) - val nextLcc: Option[Sync.UpstreamLcc] = prev - .flatMap(_.sync.upstream) - .flatMap: - case lcc: Sync.UpstreamLcc => lcc.copy(round = nextNumber).some - case _ => none val guessDate = for (prev, old) <- prevs prevDate <- prev.startsAt oldDate <- old.startsAt delta = prevDate.toEpochMilli - oldDate.toEpochMilli yield prevDate.plusMillis(delta) + val nextUrl: Option[Upstream.Url] = for + p <- prev + up <- p.sync.upstream + lcc <- up.lcc + if prevNumber.contains(lcc.round) + yield Upstream.Url(lcc.copy(round = nextNumber).pageUrl) Data( name = RelayRound.Name(guessName | s"Round ${nextNumber}"), caption = prev.flatMap(_.caption), syncSource = prev.map(Data.make).flatMap(_.syncSource), - syncLcc = nextLcc, + syncUrl = nextUrl, startsAt = guessDate, period = prev.flatMap(_.sync.period), delay = prev.flatMap(_.sync.delay), @@ -137,7 +148,7 @@ object RelayRoundForm: val list = ids.split(' ').view.flatMap(i => GameId.from(i.trim)).toList (list.sizeIs > 0 && list.sizeIs <= RelayFetch.maxChapters.value).option(GameIds(list)) - private def cleanUrl(source: String)(using mode: Mode): Option[String] = + private def cleanUrl(source: String)(using mode: Mode): Option[URL] = for url <- Try(URL.parse(source)).toOption if url.scheme == "http" || url.scheme == "https" @@ -145,26 +156,18 @@ object RelayRoundForm: // prevent common mistakes (not for security) if mode.notProd || !blocklist.exists(subdomain(host, _)) if !subdomain(host, "chess.com") || url.toString.startsWith("https://api.chess.com/pub") - yield url.toString.stripSuffix("/") - - private def validateUpstreamUrlOrLcc(s: String)(using Mode): Either[String, Sync.FetchableUpstream] = - Sync.UpstreamLcc.find(s) match - case Some(lcc) => Right(lcc) - case None => validateUpstreamUrl(s) + yield url - private def validateUpstreamUrl(s: String)(using Mode): Either[String, Sync.UpstreamUrl] = for + private def validateUpstreamUrl(s: String)(using Mode): Either[String, URL] = for url <- cleanUrl(s).toRight("Invalid source URL") url <- if !validSourcePort(url) then Left("The source URL cannot specify a port") else Right(url) - yield Sync.UpstreamUrl(url) + yield url - private def cleanUrls(source: String)(using mode: Mode): Option[List[String]] = + private def cleanUrls(source: String)(using mode: Mode): Option[List[URL]] = source.linesIterator.toList.flatMap(cleanUrl).some.filter(_.nonEmpty) - private val validPorts = Set(-1, 80, 443, 8080, 8491) - private def validSourcePort(source: String)(using mode: Mode): Boolean = - mode.notProd || - Try(URL.parse(source)).toOption.forall: url => - validPorts(url.port) + private val validPorts = Set(-1, 80, 443, 8080, 8491) + private def validSourcePort(url: URL)(using mode: Mode): Boolean = mode.notProd || validPorts(url.port) private def subdomain(host: String, domain: String) = s".$host".endsWith(s".$domain") @@ -178,7 +181,6 @@ object RelayRoundForm: "twitch.com", "youtube.com", "youtu.be", - "lichess.org", "google.com", "vk.com", "chess-results.com", @@ -192,10 +194,9 @@ object RelayRoundForm: name: RelayRound.Name, caption: Option[RelayRound.Caption], syncSource: Option[String], - syncUrl: Option[Sync.UpstreamUrl] = None, - syncUrls: Option[Sync.UpstreamUrls] = None, - syncLcc: Option[Sync.UpstreamLcc] = None, - syncIds: Option[Sync.UpstreamIds] = None, + syncUrl: Option[Upstream.Url] = None, + syncUrls: Option[Upstream.Urls] = None, + syncIds: Option[Upstream.Ids] = None, startsAt: Option[Instant] = None, finished: Option[Boolean] = None, period: Option[Seconds] = None, @@ -203,22 +204,12 @@ object RelayRoundForm: onlyRound: Option[Int] = None, slices: Option[List[RelayGame.Slice]] = None ): - def upstream: Option[Sync.Upstream] = syncSource - .match - case None => syncUrl.orElse(syncUrls).orElse(syncIds) - case Some("url") => syncUrl - case Some("urls") => syncUrls - case Some("lcc") => syncLcc - case Some("ids") => syncIds - case _ => None - .map: - case url: Sync.UpstreamUrl => - val foundLcc = for - lccId <- Sync.UpstreamLcc.findId(url) - round <- roundNumberIn(name.value) - yield Sync.UpstreamLcc(lccId, round) - foundLcc | url - case up => up + def upstream: Option[Upstream] = syncSource.match + case None => syncUrl.orElse(syncUrls).orElse(syncIds) + case Some("url") => syncUrl + case Some("urls") => syncUrls + case Some("ids") => syncIds + case _ => None def update(official: Boolean)(relay: RelayRound)(using me: Me)(using mode: Mode) = val sync = makeSync(me) @@ -264,20 +255,17 @@ object RelayRoundForm: caption = relay.caption, syncSource = relay.sync.upstream .fold("push"): - case _: Sync.UpstreamUrl => "url" - case _: Sync.UpstreamUrls => "urls" - case _: Sync.UpstreamLcc => "lcc" - case _: Sync.UpstreamIds => "ids" + case _: Upstream.Url => "url" + case _: Upstream.Urls => "urls" + case _: Upstream.Ids => "ids" .some, syncUrl = relay.sync.upstream.collect: - case url: Sync.UpstreamUrl => url, + case url: Upstream.Url => url, syncUrls = relay.sync.upstream.collect: - case url: Sync.UpstreamUrl => Sync.UpstreamUrls(List(url)) - case urls: Sync.UpstreamUrls => urls, - syncLcc = relay.sync.upstream.collect: - case lcc: Sync.UpstreamLcc => lcc, + case url: Upstream.Url => Upstream.Urls(List(url.url)) + case urls: Upstream.Urls => urls, syncIds = relay.sync.upstream.collect: - case ids: Sync.UpstreamIds => ids, + case ids: Upstream.Ids => ids, startsAt = relay.startsAt, finished = relay.finished.option(true), period = relay.sync.period, diff --git a/modules/relay/src/main/RelayRoundRepo.scala b/modules/relay/src/main/RelayRoundRepo.scala index eedde0a66ac91..74b267e162c40 100644 --- a/modules/relay/src/main/RelayRoundRepo.scala +++ b/modules/relay/src/main/RelayRoundRepo.scala @@ -10,6 +10,8 @@ final private class RelayRoundRepo(val coll: Coll)(using Executor): import RelayRoundRepo.* import BSONHandlers.given + def exists(id: RelayRoundId): Fu[Boolean] = coll.exists($id(id)) + def byTourOrderedCursor(tourId: RelayTourId) = coll .find(selectors.tour(tourId)) @@ -49,6 +51,14 @@ final private class RelayRoundRepo(val coll: Coll)(using Executor): def studyIdsOf(tourId: RelayTourId): Fu[List[StudyId]] = coll.distinctEasy[StudyId, List]("_id", selectors.tour(tourId)) + def syncTargetsOfSource(source: RelayRoundId): Funit = + coll.update + .one( + $doc("sync.until".$exists(true), "sync.upstream.roundIds" -> source), + $set("sync.nextAt" -> nowInstant) + ) + .void + private object RelayRoundRepo: object sort: diff --git a/modules/relay/src/main/RelayStatsApi.scala b/modules/relay/src/main/RelayStatsApi.scala index 42fdd86a7eaef..ea963452496c8 100644 --- a/modules/relay/src/main/RelayStatsApi.scala +++ b/modules/relay/src/main/RelayStatsApi.scala @@ -8,7 +8,7 @@ object RelayStats: type Minute = Int type Crowd = Int type Graph = List[(Minute, Crowd)] - case class RoundStats(round: RelayRound, viewers: Graph) + case class RoundStats(viewers: Graph) final class RelayStatsApi(roundRepo: RelayRoundRepo, colls: RelayColls)(using scheduler: Scheduler)(using Executor @@ -17,39 +17,22 @@ final class RelayStatsApi(roundRepo: RelayRoundRepo, colls: RelayColls)(using sc import BSONHandlers.given // on measurement by minute at most; the storage depends on it. - scheduler.scheduleWithFixedDelay(1 minute, 1 minute)(() => record()) + scheduler.scheduleWithFixedDelay(2 minutes, 2 minutes)(() => record()) - def get(id: RelayTourId): Fu[List[RoundStats]] = - colls.round - .aggregateList(RelayTour.maxRelays): framework => - import framework.* - Match($doc("tourId" -> id)) -> List( - Sort(Ascending("createdAt")), - AddFields($doc("sync.log" -> $arr())), - PipelineOperator( - $lookup.simple(colls.stats, "stats", "_id", "_id") - ), - AddFields($doc("stats" -> $doc("$first" -> "$stats"))) - ) - .map: docs => - for - doc <- docs - round <- doc.asOpt[RelayRound] - data = for - doc <- doc.getAsOpt[Bdoc]("stats") - data <- doc.getAsOpt[List[Int]]("d") - yield data - stats = data.so: - _.grouped(2) - .collect: - case List(minute, crowd) => (minute, crowd) - .toList - yield RoundStats(round, stats) + def get(id: RelayRoundId): Fu[RoundStats] = + colls.stats + .primitiveOne[List[Int]]($id(id), "d") + .mapz: + _.grouped(2) + .collect: + case List(minute, crowd) => (minute, crowd) + .toList + .map(RoundStats.apply) def setActive(id: RelayRoundId) = activeRounds.put(id) - // keep monitoring rounds for 30m after they stopped syncing - private val activeRounds = ExpireSetMemo[RelayRoundId](30 minutes) + // keep monitoring rounds for some time after they stopped syncing + private val activeRounds = ExpireSetMemo[RelayRoundId](2 hours) private def record(): Funit = for crowds <- fetchRoundCrowds diff --git a/modules/relay/src/main/RelaySync.scala b/modules/relay/src/main/RelaySync.scala index 50b8ee4b7cc9f..8a5dffc9188b7 100644 --- a/modules/relay/src/main/RelaySync.scala +++ b/modules/relay/src/main/RelaySync.scala @@ -23,7 +23,7 @@ final private class RelaySync( plan = RelayUpdatePlan(chapters, games) _ <- plan.reorder.so(studyApi.sortChapters(study.id, _)(who(study.ownerId))) updates <- plan.update.sequentially: (chapter, game) => - updateChapter(rt.tour, study, game, chapter) + updateChapter(rt, study, game, chapter) appends <- plan.append.toList.sequentially: game => createChapter(rt, study, game) result = SyncResult.Ok(updates ::: appends.flatten, games) @@ -32,14 +32,15 @@ final private class RelaySync( yield result private def updateChapter( - tour: RelayTour, + rt: RelayRound.WithTour, study: Study, game: RelayGame, chapter: Chapter ): Fu[SyncResult.ChapterResult] = for chapter <- updateInitialPosition(study.id, chapter, game) - tagUpdate <- updateChapterTags(tour, study, chapter, game) - nbMoves <- updateChapterTree(study, chapter, game)(using tour) + tagUpdate <- updateChapterTags(rt.tour, study, chapter, game) + nbMoves <- updateChapterTree(study, chapter, game)(using rt.tour) + _ <- (nbMoves > 0).so(notifier.roundBegin(rt)) yield SyncResult.ChapterResult(chapter.id, tagUpdate, nbMoves) private def createChapter( @@ -53,8 +54,6 @@ final private class RelaySync( (RelayFetch.maxChapters > nb).so: createChapter(study, game)(using rt.tour).map: chapter => SyncResult.ChapterResult(chapter.id, true, chapter.root.mainline.size).some - .flatMapz: result => - ((result.newMoves > 0).so(notifier.roundBegin(rt))).inject(result.some) private def updateInitialPosition(studyId: StudyId, chapter: Chapter, game: RelayGame): Fu[Chapter] = if game.root.mainline.sizeIs > 1 || game.root.fen == chapter.root.fen @@ -124,10 +123,11 @@ final private class RelaySync( val gameTags = game.tags.value.foldLeft(Tags(Nil)): (newTags, tag) => if !chapter.tags.value.has(tag) then newTags + tag else newTags - val newEndTag = game.ending - .ifFalse(gameTags(_.Result).isDefined) - .filterNot(end => chapter.tags(_.Result).has(end.resultText)) - .map(end => Tag(_.Result, end.resultText)) + val newEndTag = ( + game.outcome.isDefined && + gameTags(_.Result).isEmpty && + !chapter.tags(_.Result).has(game.showResult) + ).option(Tag(_.Result, game.showResult)) val tags = newEndTag.fold(gameTags)(gameTags + _) val chapterNewTags = tags.value.foldLeft(chapter.tags): (chapterTags, tag) => PgnTags(chapterTags + tag) @@ -215,8 +215,9 @@ sealed trait SyncResult: val reportKey: String object SyncResult: case class Ok(chapters: List[ChapterResult], games: RelayGames) extends SyncResult: - def nbMoves = chapters.foldLeft(0)(_ + _.newMoves) - val reportKey = "ok" + def nbMoves = chapters.foldLeft(0)(_ + _.newMoves) + def hasMovesOrTags = chapters.exists(c => c.newMoves > 0 || c.tagUpdate) + val reportKey = "ok" case object Timeout extends Exception with SyncResult with util.control.NoStackTrace: val reportKey = "timeout" override def getMessage = "In progress..." diff --git a/modules/relay/src/main/RelayTeams.scala b/modules/relay/src/main/RelayTeams.scala index 3d9f83e3e3df0..3eac1fb74b8b6 100644 --- a/modules/relay/src/main/RelayTeams.scala +++ b/modules/relay/src/main/RelayTeams.scala @@ -78,7 +78,9 @@ final class RelayTeamTable( .dataList(studyId) .map: chapters => import json.given - JsonStr(Json.stringify(Json.obj("table" -> makeTable(chapters)))) + val table = makeTable(chapters) + val ordered = ensureFirstPlayerHasWhite(table) + JsonStr(Json.stringify(Json.obj("table" -> ordered))) case class TeamWithPoints(name: String, points: Float = 0): def add(o: Option[Outcome], as: Color) = @@ -93,7 +95,8 @@ final class RelayTeamTable( def bimap[B](f: A => B, g: A => B) = Pair(f(a), g(b)) def reverse = Pair(b, a) - case class TeamGame(id: StudyChapterId, pov: Color) + case class TeamGame(id: StudyChapterId, pov: Color): + def swap = copy(pov = !pov) case class TeamMatch(teams: Pair[TeamWithPoints], games: List[TeamGame]): def is(teamNames: Pair[TeamName]) = teams.map(_.name).is(teamNames) @@ -108,6 +111,7 @@ final class RelayTeamTable( games = TeamGame(chap.id, t0Color) :: games, teams = teams.bimap(_.add(outcome, t0Color), _.add(outcome, !t0Color)) ) + def swap = copy(teams = teams.reverse, games = games.map(_.swap)) def makeTable(chapters: List[ChapterPreview]): List[TeamMatch] = chapters.reverse.foldLeft(List.empty[TeamMatch]): (table, chap) => @@ -120,6 +124,11 @@ final class RelayTeamTable( newTable = m1 :: table.filterNot(_.is(teams)) yield newTable) | table + def ensureFirstPlayerHasWhite(table: List[TeamMatch]): List[TeamMatch] = + table.map: m => + if m.games.headOption.forall(_.pov.white) then m + else m.swap + object json: import lila.common.Json.given given [A: Writes]: Writes[Pair[A]] = Writes: p => diff --git a/modules/relay/src/main/RelayTour.scala b/modules/relay/src/main/RelayTour.scala index f0c106eed16e5..8a3be5508a83a 100644 --- a/modules/relay/src/main/RelayTour.scala +++ b/modules/relay/src/main/RelayTour.scala @@ -5,11 +5,12 @@ import reactivemongo.api.bson.Macros.Annotations.Key import lila.core.i18n.Language import lila.core.misc.PicfitUrl import lila.core.id.ImageId +import java.time.LocalDate case class RelayTour( @Key("_id") id: RelayTourId, name: RelayTour.Name, - description: String, + info: RelayTour.Info, markup: Option[Markdown] = None, ownerId: UserId, createdAt: Instant, @@ -23,6 +24,7 @@ case class RelayTour( players: Option[RelayPlayersTextarea] = None, teams: Option[RelayTeamsTextarea] = None, image: Option[ImageId] = None, + dates: Option[RelayTour.Dates] = None, // denormalized from round dates pinnedStreamer: Option[UserStr] = None, pinnedStreamerImage: Option[ImageId] = None ): @@ -74,6 +76,17 @@ object RelayTour: ) type Selector = RelayTour.Tier.type => RelayTour.Tier + case class Info( + format: Option[String], + tc: Option[String], + players: Option[String] + ): + val all = List(format, tc, players).flatten + export all.nonEmpty + override def toString = all.mkString(" | ") + + case class Dates(start: Instant, end: Option[Instant]) + case class Spotlight(enabled: Boolean, language: Language, title: Option[String]): def isEmpty = !enabled && specialLanguage.isEmpty && title.isEmpty def specialLanguage: Option[Language] = (language != lila.core.i18n.defaultLanguage).option(language) @@ -85,7 +98,14 @@ object RelayTour: display: RelayRound, // which round to show on the tour link link: RelayRound, // which round to actually link to group: Option[RelayGroup.Name] - ) extends RelayRound.AndTourAndGroup + ) extends RelayRound.AndTourAndGroup: + def errors: List[String] = + val round = display + ~round.sync.log.lastErrors.some + .filter(_.nonEmpty) + .orElse: + (round.hasStarted && round.sync.upstream.isDefined && !round.sync.ongoing) + .option(List("Not syncing!")) case class WithLastRound(tour: RelayTour, round: RelayRound, group: Option[RelayGroup.Name]) extends RelayRound.AndTourAndGroup: diff --git a/modules/relay/src/main/RelayTourForm.scala b/modules/relay/src/main/RelayTourForm.scala index b5bbc133ed8b3..7a792c8fdba27 100644 --- a/modules/relay/src/main/RelayTourForm.scala +++ b/modules/relay/src/main/RelayTourForm.scala @@ -3,7 +3,7 @@ package lila.relay import play.api.data.* import play.api.data.Forms.* -import lila.common.Form.{ cleanText, formatter, into, numberIn } +import lila.common.Form.{ cleanText, formatter, into, numberIn, ISODate } import lila.core.perm.Granter import lila.core.i18n.I18nKey.streamer @@ -17,10 +17,16 @@ final class RelayTourForm(langList: lila.core.i18n.LangList): RelayTour.Spotlight.apply )(unapply) + val infoMapping = mapping( + "format" -> optional(cleanText(maxLength = 80)), + "tc" -> optional(cleanText(maxLength = 80)), + "players" -> optional(cleanText(maxLength = 120)) + )(RelayTour.Info.apply)(unapply) + val form = Form( mapping( "name" -> cleanText(minLength = 3, maxLength = 80).into[RelayTour.Name], - "description" -> cleanText(minLength = 3, maxLength = 400), + "info" -> infoMapping, "markdown" -> optional(cleanText(maxLength = 20_000).into[Markdown]), "tier" -> optional(numberIn(RelayTour.Tier.keys.keySet)), "autoLeaderboard" -> boolean, @@ -45,7 +51,7 @@ object RelayTourForm: case class Data( name: RelayTour.Name, - description: String, + info: RelayTour.Info, markup: Option[Markdown], tier: Option[RelayTour.Tier], autoLeaderboard: Boolean, @@ -61,9 +67,9 @@ object RelayTourForm: tour .copy( name = name, - description = description, + info = info, markup = markup, - tier = tier.ifTrue(Granter(_.Relay)), + tier = if Granter(_.Relay) then tier else tour.tier, autoLeaderboard = autoLeaderboard, teamTable = teamTable, players = players, @@ -77,7 +83,7 @@ object RelayTourForm: RelayTour( id = RelayTour.makeId, name = name, - description = description, + info = info, markup = markup, ownerId = me, tier = tier.ifTrue(Granter(_.Relay)), @@ -99,7 +105,7 @@ object RelayTourForm: import tg.* Data( name = tour.name, - description = tour.description, + info = tour.info, markup = tour.markup, tier = tour.tier, autoLeaderboard = tour.autoLeaderboard, diff --git a/modules/relay/src/main/RelayTourRepo.scala b/modules/relay/src/main/RelayTourRepo.scala index fed24b208718d..6c285ea803f7c 100644 --- a/modules/relay/src/main/RelayTourRepo.scala +++ b/modules/relay/src/main/RelayTourRepo.scala @@ -11,8 +11,13 @@ final private class RelayTourRepo(val coll: Coll)(using Executor): def setSyncedNow(tour: RelayTour): Funit = coll.updateField($id(tour.id), "syncedAt", nowInstant).void - def setActive(tourId: RelayTourId, active: Boolean, live: Boolean): Funit = - coll.update.one($id(tourId), $set("active" -> active, "live" -> live)).void + def denormalize( + tourId: RelayTourId, + active: Boolean, + live: Boolean, + dates: Option[RelayTour.Dates] + ): Funit = + coll.update.one($id(tourId), $set("active" -> active, "live" -> live, "dates" -> dates)).void def lookup(local: String) = $lookup.simple(coll, "tour", local, "_id") diff --git a/modules/relay/src/main/SyncLog.scala b/modules/relay/src/main/SyncLog.scala index 177098d87b2eb..e3e059884100b 100644 --- a/modules/relay/src/main/SyncLog.scala +++ b/modules/relay/src/main/SyncLog.scala @@ -10,6 +10,8 @@ case class SyncLog(events: Vector[SyncLog.Event]) extends AnyVal: def updatedAt = events.lastOption.map(_.at) + def lastErrors: List[String] = events.reverse.takeWhile(_.isKo).flatMap(_.error).toList + def add(event: SyncLog.Event) = copy( events = { diff --git a/modules/relay/src/main/ui/FormUi.scala b/modules/relay/src/main/ui/FormUi.scala index f423b2fd6be4a..bdec0f8f73cb7 100644 --- a/modules/relay/src/main/ui/FormUi.scala +++ b/modules/relay/src/main/ui/FormUi.scala @@ -6,17 +6,23 @@ import scalalib.paginator.Paginator import lila.ui.* import ScalatagsTemplate.{ *, given } import play.api.data.Form -import lila.core.id.ImageId case class FormNavigation( group: Option[RelayGroup.WithTours], tour: RelayTour, rounds: List[RelayRound], - round: Option[RelayRoundId], + roundId: Option[RelayRoundId], + sourceRound: Option[RelayRound.WithTour] = none, + targetRound: Option[RelayRound.WithTour] = none, newRound: Boolean = false ): def tourWithGroup = RelayTour.WithGroupTours(tour, group) def tourWithRounds = RelayTour.WithRounds(tour, rounds) + def round = roundId.flatMap(id => rounds.find(_.id == id)) + def featurableRound = round + .ifTrue(targetRound.isEmpty) + .filter: r => + r.sync.upstream.forall(up => up.isUrl && !up.hasLcc) final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi): import helpers.{ *, given } @@ -25,8 +31,10 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi): private def navigationMenu(nav: FormNavigation)(using Context) = def tourAndRounds(shortName: Option[RelayTour.Name]) = frag( a( - href := routes.RelayTour.edit(nav.tour.id), + href := routes.RelayTour.edit(nav.tour.id), + dataIcon := Icon.RadioTower, cls := List( + "text" -> true, "relay-form__subnav__tour-parent" -> shortName.isDefined, "active" -> (nav.round.isEmpty && !nav.newRound) ) @@ -37,7 +45,7 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi): nav.rounds.map: r => a( href := routes.RelayRound.edit(r.id), - cls := List("subnav__subitem text" -> true, "active" -> nav.round.has(r.id)), + cls := List("subnav__subitem text" -> true, "active" -> nav.roundId.has(r.id)), dataIcon := ( if r.finished then Icon.Checkmark else if r.hasStarted then Icon.DiscBig @@ -45,8 +53,12 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi): ) )(r.name), a( - href := routes.RelayRound.create(nav.tour.id), - cls := List("subnav__subitem text" -> true, "active" -> nav.newRound), + href := routes.RelayRound.create(nav.tour.id), + cls := List( + "subnav__subitem text" -> true, + "active" -> nav.newRound, + "button" -> (nav.rounds.isEmpty && !nav.newRound) + ), dataIcon := Icon.PlusButton )(trb.addRound()) ) @@ -99,16 +111,27 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi): ) ), standardFlash, - inner(form, routes.RelayRound.create(nav.tour.id), nav.tour, create = true) + inner(form, routes.RelayRound.create(nav.tour.id), nav) ) - def edit(r: RelayRound, form: Form[RelayRoundForm.Data], nav: FormNavigation)(using Context) = + def edit( + r: RelayRound, + form: Form[RelayRoundForm.Data], + nav: FormNavigation + )(using Context) = page(r.name.value, nav): val rt = r.withTour(nav.tour) frag( boxTop(h1(a(href := rt.path)(rt.fullName))), standardFlash, - inner(form, routes.RelayRound.update(r.id), nav.tour, create = false), + nav.targetRound.map: tr => + flashMessage("success")( + "Your tournament round is officially broadcasted by Lichess!", + br, + strong(a(href := tr.path, cls := "text", dataIcon := Icon.RadioTower)(tr.fullName)), + "." + ), + inner(form, routes.RelayRound.update(r.id), nav), div(cls := "relay-form__actions")( postForm(action := routes.RelayRound.reset(r.id))( submitButton( @@ -126,14 +149,38 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi): ) ) - private def inner(form: Form[RelayRoundForm.Data], url: play.api.mvc.Call, t: RelayTour, create: Boolean)( - using ctx: Context - ) = + private def inner( + form: Form[RelayRoundForm.Data], + url: play.api.mvc.Call, + nav: FormNavigation + )(using ctx: Context) = + val lccWarning = nav.round + .flatMap(_.sync.upstream) + .exists(_.hasLcc) + .option: + flashMessage("box relay-form__lcc-deprecated")( + p(strong("Please use the ", a(href := broadcasterUrl)("Lichess Broadcaster App"))), + p( + "LiveChessCloud support is deprecated and will be removed soon.", + br, + "If you need help, please contact us at broadcast@lichess.org." + ) + ) + val contactUsForOfficial = nav.featurableRound.isDefined + .option: + flashMessage("box relay-form__contact-us")( + p( + "Is this a tournament you organize? Do you want Lichess to feature it on the ", + a(href := routes.RelayTour.index(1))("broadcast page"), + "?" + ), + p(trans.contact.sendEmailAt("broadcast@lichess.org")) + ) postForm(cls := "form3", action := url)( (!Granter.opt(_.StudyAdmin)).option: div(cls := "form-group")( div(cls := "form-group")(ui.howToUse), - (create && t.createdAt.isBefore(nowInstant.minusMinutes(1))).option: + (nav.round.isEmpty && nav.tour.createdAt.isBefore(nowInstant.minusMinutes(1))).option: p(dataIcon := Icon.InfoCircle, cls := "text"): trb.theNewRoundHelp() ) @@ -141,71 +188,69 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi): form3.globalError(form), form3.split( form3.group(form("name"), trb.roundName(), half = true)(form3.input(_)(autofocus)), - Granter - .opt(_.StudyAdmin) - .option( - form3.group( - form("caption"), - "Homepage spotlight custom round name", - help = raw("Leave empty to use the round name").some, - half = true - ): - form3.input(_) - ) + form3.group( + form("startsAt"), + trb.startDate(), + help = trb.startDateHelp().some, + half = true + )(form3.flatpickr(_, minDate = None)) ), - form3.fieldset("Source")(cls := "box-pad")( + form3.fieldset("Source", toggle = true.some)(cls := "box-pad")( form3.group( form("syncSource"), "Where do the games come from?" )(form3.select(_, RelayRoundForm.sourceTypes)), - form3.group( - form("syncUrl"), - trb.sourceSingleUrl(), - help = trb.sourceUrlHelp().some - )(form3.input(_))(cls := "relay-form__sync relay-form__sync-url"), - div(cls := "relay-form__sync relay-form__sync-lcc none")( - (!Granter.opt(_.Relay)).option( - flashMessage("box")( - p(strong("Please use the ", a(href := broadcasterUrl)("Lichess Broadcaster App"))), - p( - "LiveChessCloud support is deprecated and will be removed soon.", - br, - "If you need help, please contact us at broadcast@lichess.org." - ) + div(cls := "relay-form__sync relay-form__sync-url")( + lccWarning.orElse(contactUsForOfficial), + form3.group( + form("syncUrl"), + trb.sourceSingleUrl(), + help = trb.sourceUrlHelp().some + )(form3.input(_)), + nav.sourceRound.map: source => + flashMessage("round-push")( + "Getting real-time updates from ", + strong(a(href := source.path)(source.fullName)), + br, + "Owner: ", + userIdLink(source.tour.ownerId.some), + br, + "Delay: ", + source.round.sync.delay.fold("0")(_.toString), + "s", + br, + "Start: ", + source.round.startedAt.orElse(source.round.startsAt).fold(frag("unscheduled"))(momentFromNow), + br, + "Last sync: ", + source.round.sync.log.events.lastOption.map: event => + frag( + momentFromNow(event.at), + br, + event.error match + case Some(err) => s"❌ $err" + case _ => s"✅ ${event.moves} moves" + ) ) - ), - form3.split( - form3.group( - form("syncLcc.id"), - "Tournament ID", - help = frag( - "From the LCC page URL. The ID looks like this: ", - pre("f1943ec6-4992-45d9-969d-a0aff688b404") - ).some, - half = true - )(form3.input(_)), - form3.group( - form("syncLcc.round"), - trb.roundNumber(), - half = true - )(form3.input(_, typ = "number")) - ) ), form3.group( form("syncUrls"), "Multiple source URLs, one per line.", help = frag("The games will be combined in the order of the URLs.").some, half = false - )(form3.textarea(_)(rows := 5))(cls := "relay-form__sync relay-form__sync-urls none"), + )(field => + frag(lccWarning, form3.textarea(field)(rows := 5, spellcheck := "false", cls := "monospace")) + )(cls := "relay-form__sync relay-form__sync-urls none"), form3.group( form("syncIds"), trb.sourceGameIds(), half = false )(form3.input(_))(cls := "relay-form__sync relay-form__sync-ids none"), div(cls := "form-group relay-form__sync relay-form__sync-push none")( + contactUsForOfficial, p( - "Send your local games to Lichess using ", - a(href := "https://github.com/lichess-org/broadcaster")(lila.relay.broadcasterUrl), + "Send your local games to Lichess using the ", + a(href := broadcasterUrl)("Lichess Broadcaster App"), "." ) ), @@ -248,44 +293,50 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi): )(form3.input(_)) ) ), - form3.split( - form3.group( - form("startsAt"), - trb.startDate(), - help = trb.startDateHelp().some, - half = true - )(form3.flatpickr(_, minDate = None)), - form3.checkbox( - form("finished"), - trb.completed(), - help = trb.completedHelp().some, - half = true + form3.fieldset("Advanced", toggle = nav.round.exists(r => r.sync.delay.isDefined).some)( + form3.split( + form3.group( + form("delay"), + raw("Delay in seconds"), + help = frag( + "Optional, how long to delay moves coming from the source.", + br, + "Add this delay to the start date of the event. E.g. if a tournament starts at 20:00 with a delay of 15 minutes, set the start date to 20:15." + ).some, + half = true + )(form3.input(_, typ = "number")), + form3.checkbox( + form("finished"), + trb.completed(), + help = trb.completedHelp().some, + half = true + ) ) ), - form3.split( - form3.group( - form("delay"), - raw("Delay in seconds"), - help = frag( - "Optional, how long to delay moves coming from the source.", - br, - "Add this delay to the start date of the event. E.g. if a tournament starts at 20:00 with a delay of 15 minutes, set the start date to 20:15." - ).some, - half = true - )(form3.input(_, typ = "number")), - Granter - .opt(_.StudyAdmin) - .option( - form3.group( - form("period"), - trb.periodInSeconds(), - help = trb.periodInSecondsHelp().some, - half = true - )(form3.input(_, typ = "number")) + Granter + .opt(_.StudyAdmin) + .option( + form3.fieldset("Broadcast admin", toggle = false.some)( + form3.split( + form3.group( + form("caption"), + "Homepage spotlight custom round name", + help = raw("Leave empty to use the round name").some, + half = true + ): + form3.input(_) + , + form3.group( + form("period"), + trb.periodInSeconds(), + help = trb.periodInSecondsHelp().some, + half = true + )(form3.input(_, typ = "number")) + ) ) - ), + ), form3.actions( - a(href := routes.RelayTour.show(t.slug, t.id))(trans.site.cancel()), + a(href := routes.RelayTour.show(nav.tour.slug, nav.tour.id))(trans.site.cancel()), form3.submit(trans.site.apply()) ) ) @@ -319,6 +370,7 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi): page(nav.tour.name.value, menu = Right(nav)): frag( boxTop(h1(a(href := routes.RelayTour.show(nav.tour.slug, nav.tour.id))(nav.tour.name))), + standardFlash, image(nav.tour), postForm(cls := "form3", action := routes.RelayTour.update(nav.tour.id))( inner(form, nav.tourWithGroup.some), @@ -352,173 +404,208 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi): frag( (!Granter.opt(_.StudyAdmin)).option(div(cls := "form-group")(ui.howToUse)), form3.globalError(form), - form3.split( - form3.group(form("name"), trb.tournamentName(), half = true)(form3.input(_)(autofocus)), - Granter - .opt(_.StudyAdmin) - .option( - form3.group( - form("spotlight.title"), - "Homepage spotlight custom tournament name", - help = raw("Leave empty to use the tournament name").some, - half = true - )(form3.input(_)) - ) + form3.group(form("name"), trb.tournamentName())(form3.input(_)(autofocus)), + form3.fieldset("Optional details", toggle = tg.exists(_.tour.info.nonEmpty).some)( + form3.split( + form3.group( + form("info.format"), + "Tournament format", + help = frag("""e.g. "8-player round-robin" or "5-round Swiss"""").some, + half = true + )(form3.input(_)), + form3.group( + form("info.tc"), + "Time control", + help = frag(""""Classical" or "Rapid" or "Rapid & Blitz"""").some, + half = true + )(form3.input(_)) + ), + form3.group( + form("info.players"), + "Top players", + help = frag("Mention up to 4 of the best players participating").some + )(form3.input(_)), + form3.group( + form("markdown"), + trb.fullDescription(), + help = trb + .fullDescriptionHelp( + a( + href := "https://guides.github.com/features/mastering-markdown/", + targetBlank + )("Markdown"), + 20000.localize + ) + .some + )(form3.textarea(_)(rows := 10)) ), - form3.group(form("description"), trb.tournamentDescription())(form3.textarea(_)(rows := 2)), - form3.group( - form("markdown"), - trb.fullDescription(), - help = trb - .fullDescriptionHelp( - a( - href := "https://guides.github.com/features/mastering-markdown/", - targetBlank - )("Markdown"), - 20000.localize + form3 + .fieldset("Features", toggle = tg.map(_.tour).exists(t => t.autoLeaderboard || t.teamTable).some)( + form3.split( + form3.checkbox( + form("autoLeaderboard"), + trb.automaticLeaderboard(), + help = trb.automaticLeaderboardHelp().some + ), + form3.checkbox( + form("teamTable"), + "Team tournament", + help = frag("Show a team leaderboard. Requires WhiteTeam and BlackTeam PGN tags.").some + ) ) - .some - )(form3.textarea(_)(rows := 10)), - form3.split( - form3.checkbox( - form("autoLeaderboard"), - trb.automaticLeaderboard(), - help = trb.automaticLeaderboardHelp().some ), - form3.checkbox( - form("teamTable"), - "Team tournament", - help = frag("Show a team leaderboard. Requires WhiteTeam and BlackTeam PGN tags.").some - ) - ), - form3.split( - form3.group( - form("players"), - trb.replacePlayerTags(), - help = frag( // do not translate - "One line per player, formatted as such:", - pre("player name = FIDE ID"), - "Example:", - pre("""Magnus Carlsen = 1503014"""), - "Player names ignore case and punctuation, and match all possible combinations of 2 words:", - br, - """"Jorge Rick Vito" will match "Jorge Rick", "jorge vito", "Rick, Vito", etc.""", - br, - "If the player is NM or WNM, you can:", - pre("""Player Name = FIDE ID / Title"""), - "Alternatively, you may set tags manually, like so:", - pre("player name / rating / title / new name"), - "All values are optional. Example:", - pre("""Magnus Carlsen / 2863 / GM + form3.fieldset( + "Players & Teams", + toggle = List("players", "teams").exists(k => form(k).value.exists(_.nonEmpty)).some + )( + form3.split( + form3.group( + form("players"), + trb.replacePlayerTags(), + help = frag( // do not translate + "One line per player, formatted as such:", + pre("player name = FIDE ID"), + "Example:", + pre("""Magnus Carlsen = 1503014"""), + "Player names ignore case and punctuation, and match all possible combinations of 2 words:", + br, + """"Jorge Rick Vito" will match "Jorge Rick", "jorge vito", "Rick, Vito", etc.""", + br, + "If the player is NM or WNM, you can:", + pre("""Player Name = FIDE ID / Title"""), + "Alternatively, you may set tags manually, like so:", + pre("player name / rating / title / new name"), + "All values are optional. Example:", + pre("""Magnus Carlsen / 2863 / GM YouGotLittUp / 1890 / / Louis Litt""") - ).some, - half = true - )(form3.textarea(_)(rows := 3)), - form3.group( - form("teams"), - "Optional: assign players to teams", - help = frag( // do not translate - "One line per player, formatted as such:", - pre("Team name; Fide Id or Player name"), - "Example:", - pre("""Team Cats ; 3408230 + ).some, + half = true + )(form3.textarea(_)(rows := 3, spellcheck := "false", cls := "monospace")), + form3.group( + form("teams"), + "Optional: assign players to teams", + help = frag( // do not translate + "One line per player, formatted as such:", + pre("Team name; Fide Id or Player name"), + "Example:", + pre("""Team Cats ; 3408230 Team Dogs ; Scooby Doo"""), - "By default the PGN tags WhiteTeam and BlackTeam are used." - ).some, - half = true - )(form3.textarea(_)(rows := 3)) + "By default the PGN tags WhiteTeam and BlackTeam are used." + ).some, + half = true + )(form3.textarea(_)(rows := 3, spellcheck := "false", cls := "monospace")) + ) ), if Granter.opt(_.Relay) then frag( - tg.isDefined.option(grouping(form)), - form3.split( - form3.group( - form("tier"), - raw("Official Lichess broadcast tier"), - help = raw("Feature on /broadcast - for admins only").some, - half = true - )(form3.select(_, RelayTour.Tier.options)) - ) - ) - else form3.hidden(form("tier")), - Granter - .opt(_.StudyAdmin) - .option( - frag( + form3.fieldset("Broadcast admin", toggle = true.some)( + tg.isDefined.option(grouping(form)), form3.split( - form3.checkbox( - form("spotlight.enabled"), - "Show a homepage spotlight", - help = raw("As a Big Blue Button - for admins only").some, - half = true - ), form3.group( - form("spotlight.lang"), - "Homepage spotlight language", - help = - raw("Only show to users who speak this language. English is shown to everyone.").some, + form("tier"), + raw("Official Lichess broadcast tier"), + help = raw("Feature on /broadcast - for admins only").some, half = true - ): - form3.select(_, langList.popularLanguagesForm.choices) + )(form3.select(_, RelayTour.Tier.options)), + Granter + .opt(_.StudyAdmin) + .option( + form3.checkbox( + form("spotlight.enabled"), + "Show a homepage spotlight", + help = raw("As a Big Blue Button - for admins only").some, + half = true + ) + ) ), - tg.map: t => - details( - summary("Pinned streamer"), - div( - cls := "relay-pinned-streamer-edit", - data("post-url") := routes.RelayTour.image(t.tour.id, "pinnedStreamerImage".some) - )( - div( + Granter + .opt(_.StudyAdmin) + .option( + frag( + form3.split( form3.group( - form("pinnedStreamer"), - "Pinned streamer", - help = frag( - p("The pinned streamer is featured even when they're not watching the broadcast."), - p("An optional placeholder image will embed their stream when clicked."), - p( - "To upload one, you must first submit this form with a pinned streamer. " - + "Then return to this page and choose an image." - ) - ).some + form("spotlight.title"), + "Homepage spotlight custom tournament name", + help = raw("Leave empty to use the tournament name").some, + half = true )(form3.input(_)), - span( - button(tpe := "button", cls := "button streamer-select-image")("select image"), - button( - tpe := "button", - cls := "button button-empty button-red streamer-delete-image", + form3.group( + form("spotlight.lang"), + "Homepage spotlight language", + help = raw( + "Only show to users who speak this language. English is shown to everyone." + ).some, + half = true + ): + form3.select(_, langList.popularLanguagesForm.choices) + ), + tg.map: t => + form3.fieldset("Pinned streamer", toggle = form("pinnedStreamer").value.isDefined.some)( + div( + cls := "relay-pinned-streamer-edit", data("post-url") := routes.RelayTour.image(t.tour.id, "pinnedStreamerImage".some) - )("delete image") + )( + div( + form3.group( + form("pinnedStreamer"), + "Pinned streamer", + help = frag( + p( + "The pinned streamer is featured even when they're not watching the broadcast." + ), + p("An optional placeholder image will embed their stream when clicked."), + p( + "To upload one, you must first submit this form with a pinned streamer. " + + "Then return to this page and choose an image." + ) + ).some + )(form3.input(_)), + span( + button(tpe := "button", cls := "button streamer-select-image")("select image"), + button( + tpe := "button", + cls := "button button-empty button-red streamer-delete-image", + data("post-url") := routes.RelayTour + .image(t.tour.id, "pinnedStreamerImage".some) + )("delete image") + ) + ), + ui.thumbnail(t.tour.pinnedStreamerImage, _.Size.Small16x9)( + cls := List( + "streamer-drop-target" -> true, + "user-image" -> t.tour.pinnedStreamerImage.isDefined + ), + attr("draggable") := "true" + ) + ) ) - ), - ui.thumbnail(t.tour.pinnedStreamerImage, _.Size.Small16x9)( - cls := List( - "streamer-drop-target" -> true, - "user-image" -> t.tour.pinnedStreamerImage.isDefined - ), - attr("draggable") := "true" - ) ) ) ) ) + else form3.hidden(form("tier")) ) private def image(t: RelayTour)(using ctx: Context) = - div(cls := "relay-image-edit", data("post-url") := routes.RelayTour.image(t.id))( - ui.thumbnail(t.image, _.Size.Small)( - cls := List("drop-target" -> true, "user-image" -> t.image.isDefined), - attr("draggable") := "true" - ), + form3.fieldset("Image", toggle = true.some): div( - p("Upload a beautiful image to represent your tournament."), - p("The image must be twice as wide as it is tall. Recommended resolution: 1000x500."), - p( - "A picture of the city where the tournament takes place is a good idea, but feel free to design something different." + cls := "form-group relay-image-edit", + data("post-url") := routes.RelayTour.image(t.id) + )( + ui.thumbnail(t.image, _.Size.Small)( + cls := List("drop-target" -> true, "user-image" -> t.image.isDefined), + attr("draggable") := "true" ), - p(trans.streamer.maxSize(s"${lila.memo.PicfitApi.uploadMaxMb}MB.")), - form3.file.selectImage() + div( + p("Upload a beautiful image to represent your tournament."), + p("The image must be twice as wide as it is tall. Recommended resolution: 1000x500."), + p( + "A picture of the city where the tournament takes place is a good idea, but feel free to design something different." + ), + p(trans.streamer.maxSize(s"${lila.memo.PicfitApi.uploadMaxMb}MB.")), + form3.file.selectImage() + ) ) - ) private def grouping(form: Form[RelayTourForm.Data])(using Context) = form3.split(cls := "relay-form__grouping")( @@ -526,7 +613,7 @@ Team Dogs ; Scooby Doo"""), form("grouping"), "Optional: assign tournaments to a group", half = true - )(form3.textarea(_)(rows := 5)), + )(form3.textarea(_)(rows := 5, spellcheck := "false", cls := "monospace")), div(cls := "form-group form-half form-help")( // do not translate "First line is the group name. Subsequent lines are the tournament IDs and names in the group. Names are facultative and only used for display in this textarea.", br, diff --git a/modules/relay/src/main/ui/RelayTourUi.scala b/modules/relay/src/main/ui/RelayTourUi.scala index 54defb524d4d6..92182daee011b 100644 --- a/modules/relay/src/main/ui/RelayTourUi.scala +++ b/modules/relay/src/main/ui/RelayTourUi.scala @@ -33,6 +33,7 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi): pageMenu("index"), div(cls := "page-menu__content box box-pad")( boxTop(h1(trc.liveBroadcasts()), searchForm("")), + Granter.opt(_.StudyAdmin).option(adminIndex(active)), nonEmptyTier(_.BEST, "best"), nonEmptyTier(_.HIGH, "high"), nonEmptyTier(_.NORMAL, "normal"), @@ -49,6 +50,16 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi): ) ) + private def adminIndex(active: List[RelayTour.ActiveWithSomeRounds])(using Context) = + val errored = active.flatMap(a => a.errors.some.filter(_.nonEmpty).map(a -> _)) + errored.nonEmpty.option: + div(cls := "relay-index__admin")( + h2("Ongoing broadcasts with errors"), + st.section(cls := "relay-cards"): + errored.map: (tr, errors) => + card.render(tr.copy(link = tr.display), live = _.display.hasStarted, errors = errors.take(5)) + ) + private def listLayout(title: String, menu: Tag)(body: Modifier*)(using Context) = Page(trc.liveBroadcasts.txt()) .css("bits.relay.index") @@ -64,9 +75,20 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi): renderPager(asRelayPager(pager), query)(cls := "relay-cards--search") ) - def byOwner(pager: Paginator[RelayTour | WithLastRound], owner: LightUser)(using Context) = + def byOwner(pager: Paginator[RelayTour | WithLastRound], owner: LightUser)(using ctx: Context) = listLayout(trc.liveBroadcasts.txt(), pageMenu("by", owner.some))( - boxTop(h1(lightUserLink(owner), " ", trc.liveBroadcasts())), + boxTop( + h1( + if ctx.is(owner) + then trc.myBroadcasts() + else frag(lightUserLink(owner), " ", trc.liveBroadcasts()) + ), + div(cls := "box__top__actions")( + a(href := routes.RelayTour.form, cls := "button button-green text", dataIcon := Icon.PlusButton)( + trc.newBroadcast() + ) + ) + ), standardFlash, renderPager(pager, owner = owner.some) ) @@ -92,7 +114,7 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi): boxTop: ui.broadcastH1(t.name) , - h2(t.description), + h2(t.info.toString), markup.map: html => frag( hr, @@ -102,16 +124,6 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi): ) ) - def stats(t: RelayTour, stats: List[RelayStats.RoundStats])(using Context) = - import JsonView.given - Page(s"${t.name.value} - Stats") - .css("bits.relay.index") - .js(PageModule("bits.relayStats", Json.obj("rounds" -> stats))): - main(cls := "relay-tour page box box-pad")( - boxTop(h1(a(href := routes.RelayTour.show(t.slug, t.id).url)(t.name), " - Stats")), - "Here, a graph shows the number of viewers over time." - ) - def page(title: String, pageBody: Frag, active: String)(using Context): Page = Page(title) .css("bits.page") @@ -128,7 +140,10 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi): lila.ui.bits.pageMenuSubnav( a(href := routes.RelayTour.index(), cls := menu.activeO("index"))(trans.broadcast.broadcasts()), ctx.me.map: me => - a(href := routes.RelayTour.by(me.username, 1), cls := by.exists(_.is(me)).option("active")): + a( + href := routes.RelayTour.by(me.username, 1), + cls := (menu == "new" || by.exists(_.is(me))).option("active") + ): trans.broadcast.myBroadcasts() , by.filterNot(ctx.is) @@ -148,12 +163,13 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi): "Private Broadcasts" ) ), - a(href := routes.RelayTour.form, cls := menu.activeO("new"))(trans.broadcast.newBroadcast()), a(href := routes.RelayTour.calendar, cls := menu.activeO("calendar"))(trans.site.tournamentCalendar()), a(href := routes.RelayTour.help, cls := menu.activeO("help"))(trans.broadcast.aboutBroadcasts()), div(cls := "sep"), - a(cls := menu.active("players"), href := routes.Fide.index(1))("FIDE players"), - a(cls := menu.active("federations"), href := routes.Fide.federations(1))("FIDE federations") + a(cls := menu.active("players"), href := routes.Fide.index(1))(trans.broadcast.fidePlayers()), + a(cls := menu.active("federations"), href := routes.Fide.federations(1))( + trans.broadcast.fideFederations() + ) ) private object card: @@ -168,7 +184,9 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi): private def image(t: RelayTour) = t.image.fold(ui.thumbnail.fallback(cls := "relay-card__image")): id => img(cls := "relay-card__image", src := ui.thumbnail.url(id, _.Size.Small)) - def render[A <: RelayRound.AndTourAndGroup](tr: A, live: A => Boolean)(using Context) = + def render[A <: RelayRound.AndTourAndGroup](tr: A, live: A => Boolean, errors: List[String] = Nil)(using + Context + ) = link(tr.tour, tr.path, live(tr))( image(tr.tour), span(cls := "relay-card__body")( @@ -186,7 +204,9 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi): else tr.display.startedAt.orElse(tr.display.startsAt).map(momentFromNow(_)) ), h3(cls := "relay-card__title")(tr.group.fold(tr.tour.name.value)(_.value)), - span(cls := "relay-card__desc")(tr.tour.description) + if errors.nonEmpty + then ul(cls := "relay-card__errors")(errors.map(li(_))) + else tr.tour.info.players.map(span(cls := "relay-card__desc")(_)) ) ) @@ -195,7 +215,7 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi): image(t), span(cls := "relay-card__body")( h3(cls := "relay-card__title")(t.name), - span(cls := "relay-card__desc")(t.description) + span(cls := "relay-card__desc")(t.info.toString) ) ) diff --git a/modules/relay/src/main/ui/RelayUi.scala b/modules/relay/src/main/ui/RelayUi.scala index 7d551ed7ae657..6c87e56ad4947 100644 --- a/modules/relay/src/main/ui/RelayUi.scala +++ b/modules/relay/src/main/ui/RelayUi.scala @@ -50,7 +50,7 @@ final class RelayUi(helpers: Helpers)( .graph( title = rt.fullName, url = s"$netBaseUrl${rt.path}", - description = shorten(rt.tour.description, 152) + description = shorten(rt.tour.info.toString, 152) ): main(cls := "analyse is-relay has-relay-tour")( div(cls := "box relay-tour")( @@ -119,8 +119,6 @@ final class RelayUi(helpers: Helpers)( import trans.broadcast as trb List( trb.addRound, - trb.broadcastUrl, - trb.currentRoundUrl, trb.currentGameUrl, trb.downloadAllRounds, trb.editRoundStudy diff --git a/modules/relay/src/test/GameJsonTest.scala b/modules/relay/src/test/GameJsonTest.scala index 2175da1c05393..a4da810901c28 100644 --- a/modules/relay/src/test/GameJsonTest.scala +++ b/modules/relay/src/test/GameJsonTest.scala @@ -1,5 +1,7 @@ package lila.relay +import chess.format.pgn.Tags + class GameJsonTest extends munit.FunSuite: test("toPgn"): @@ -124,4 +126,4 @@ class GameJsonTest extends munit.FunSuite: """d4 { [%clk 0:30:52] } e6 { [%clk 0:30:18] } Nf3 { [%clk 0:31:02] } Nc6 { [%clk 0:30:21] } e4 { [%clk 0:30:51] } Bb4+ { [%clk 0:29:01] } Nc3 { [%clk 0:31:11] } Bd6 { [%clk 0:28:56] } Bc4 { [%clk 0:30:31] } Nf6 { [%clk 0:29:02] } e5 { [%clk 0:30:00] } Be7 { [%clk 0:27:42] } exf6 { [%clk 0:29:43] } Bxf6 { [%clk 0:27:57] } O-O { [%clk 0:29:23] } O-O { [%clk 0:28:22] } Qd3 { [%clk 0:28:50] } Nb4 { [%clk 0:28:25] } Qd2 { [%clk 0:28:47] } d6 { [%clk 0:28:16] } a3 { [%clk 0:28:58] } Nc6 Ng5 Bxg5 { [%clk 0:26:57] } Qxg5 { [%clk 0:29:06] } Qd7 { [%clk 0:26:54] } Bd3 { [%clk 0:29:05] } f6 { [%clk 0:26:47] } Qh4 { [%clk 0:24:44] } g5 { [%clk 0:25:47] } Qxh7+ { [%clk 0:25:09] } Qxh7 { [%clk 0:24:25] } Bxh7+ { [%clk 0:25:32] } Kxh7 { [%clk 0:23:45] } Nb5 { [%clk 0:25:51] } Rf7 { [%clk 0:23:30] } Rd1 { [%clk 0:25:30] } Rg7 { [%clk 0:23:39] } d5 { [%clk 0:24:41] } Ne5 { [%clk 0:23:46] } Nd4 { [%clk 0:24:29] } Rg6 { [%clk 0:23:50] } Nxe6 { [%clk 0:24:48] } Rh6 f4 { [%clk 0:24:55] } Nf3+ gxf3 { [%clk 0:22:47] } Bxe6 { [%clk 0:21:57] } dxe6 { [%clk 0:23:09] } Re8 { [%clk 0:22:07] } fxg5 { [%clk 0:22:45] } fxg5 { [%clk 0:21:57] } Bxg5 { [%clk 0:23:10] } Rg6 { [%clk 0:22:05] } h4 { [%clk 0:22:43] } Rexe6 { [%clk 0:22:27] } Re1 { [%clk 0:22:30] } Re5 { [%clk 0:20:54] } Rxe5 { [%clk 0:22:31] } dxe5 { [%clk 0:21:06] } Rd1 { [%clk 0:22:49] } Rc6 { [%clk 0:21:02] } c3 { [%clk 0:23:14] } Rb6 { [%clk 0:21:11] } Rd7+ { [%clk 0:23:36] } Kg6 { [%clk 0:21:23] } Bc1 { [%clk 0:23:33] } c5 { [%clk 0:21:29] } Rd5 { [%clk 0:23:58] } Kh5 { [%clk 0:21:27] } Rxe5+ { [%clk 0:24:15] } Kxh4 { [%clk 0:21:40] } Rxc5 { [%clk 0:24:34] } Rg6+ { [%clk 0:22:01] } Kf2 { [%clk 0:24:50] } Rg3 { [%clk 0:22:06] } Rh5+ { [%clk 0:24:52] } Kxh5 { [%clk 0:22:13] } Kxg3 { [%clk 0:25:21] } b5 { [%clk 0:22:28] } Kf4 { [%clk 0:25:49] } a5 { [%clk 0:22:26] } Ke5 { [%clk 0:26:11] } Kh4 { [%clk 0:22:34] } Kd5 { [%clk 0:26:34] } Kg3 { [%clk 0:22:34] } Kc5 { [%clk 0:26:56] } Kxf3 { [%clk 0:22:40] } Kxb5 { [%clk 0:27:18] } Ke2 { [%clk 0:22:54] } c4 { [%clk 0:27:47] } Kd1 a4 Kxc1 { [%clk 0:22:31] } c5 { [%clk 0:28:12] } Kxb2 { [%clk 0:22:29] } c6 Kc3 { [%clk 0:22:28] } c7 { [%clk 0:29:00] } Kd4 { [%clk 0:22:36] } c8=Q Kd5 Qe8 { [%clk 0:28:38] } Kd6 { [%clk 0:22:37] } Qe4 { [%clk 0:28:56] } Kc7 { [%clk 0:22:47] } Qe6 { [%clk 0:29:15] } Kb7 { [%clk 0:22:55] } Qd7+ { [%clk 0:29:29] } Kb8 Kb6 Ka8 { [%clk 0:22:45] } Qc8# { [%clk 0:29:56] }""" val game = RelayFetch.DgtJson.GameJson(moves, None) - assertEquals(game.toPgn().value.trim, expected) + assertEquals(game.toPgn(Tags.empty).value.trim, expected) diff --git a/modules/report/src/main/Reason.scala b/modules/report/src/main/Reason.scala index 53f23696ed8f2..c03bc9cafad8d 100644 --- a/modules/report/src/main/Reason.scala +++ b/modules/report/src/main/Reason.scala @@ -3,33 +3,34 @@ package lila.report import scalalib.Iso import lila.core.user.Me -sealed trait Reason: - - def key = toString.toLowerCase - - def name = toString - - def isComm = this == Reason.Comm || this == Reason.Sexism +enum Reason: + case Cheat + case Stall + case Boost + case Comm // BC + case Sexism // BC + case VerbalAbuse + case Violence + case Harass + case SelfHarm + case Hate + case Spam + case Username + case Other + // auto reports: + case Playbans + case AltPrint + def key = toString.toLowerCase + def name = if this == AltPrint then "Print" else toString + def isComm = Reason.comm(this) object Reason: - - case object Cheat extends Reason - case object CheatPrint extends Reason: // BC, replaced with AltPrint - override def name = "Print" - case object AltPrint extends Reason: - override def name = "Print" - case object Comm extends Reason: - def flagText = "[FLAG]" - case object Boost extends Reason - case object Username extends Reason - case object Sexism extends Reason - case object Other extends Reason - case object Playbans extends Reason - - val all = List(Cheat, AltPrint, Comm, Boost, Username, Sexism, Other, CheatPrint) + val all = values.toList val keys = all.map(_.key) val byKey = all.mapBy(_.key) - val autoBlock = Set(Comm, Sexism) + val comm = Set(Comm, Sexism, VerbalAbuse, Violence, Harass, SelfHarm, Hate, Spam) + val autoBlock = comm + val flagText = "[FLAG]" given Iso.StringIso[Reason] = Iso.string(k => byKey.getOrElse(k, Other), _.key) @@ -41,14 +42,14 @@ object Reason: def isComm = reason.isComm def isCheat = reason == Cheat def isOther = reason == Other - def isPrint = reason == AltPrint || reason == CheatPrint + def isPrint = reason == AltPrint def isBoost = reason == Boost def is(reason: Reason.type => Reason) = this.reason == reason(Reason) def isGranted(reason: Reason)(using Me) = import lila.core.perm.Granter reason match - case Cheat => Granter(_.MarkEngine) - case Comm | Sexism => Granter(_.Shadowban) - case Boost => Granter(_.MarkBooster) - case AltPrint | CheatPrint | Playbans | Username | Other => Granter(_.Admin) + case Cheat => Granter(_.MarkEngine) + case r if r.isComm => Granter(_.Shadowban) + case Boost => Granter(_.MarkBooster) + case _ => Granter(_.Admin) diff --git a/modules/report/src/main/Report.scala b/modules/report/src/main/Report.scala index 3f7fc35e646f6..4abc4537bea87 100644 --- a/modules/report/src/main/Report.scala +++ b/modules/report/src/main/Report.scala @@ -108,7 +108,7 @@ object Report: def byLichess = by.is(ReporterId.lichess) - def isFlag = text.startsWith(Reason.Comm.flagText) + def isFlag = text.startsWith(Reason.flagText) case class Done(by: ModId, at: Instant) diff --git a/modules/report/src/main/ReportApi.scala b/modules/report/src/main/ReportApi.scala index 568a39e68b596..693d2946525e5 100644 --- a/modules/report/src/main/ReportApi.scala +++ b/modules/report/src/main/ReportApi.scala @@ -52,38 +52,33 @@ final class ReportApi( val ignoreReport = c.reporter.user.marks.reportban && !c.reason.isComm (!ignoreReport && !isAlreadySlain(c)).so { scorer(c).map(_.withScore(score)).flatMap { case scored @ Candidate.Scored(candidate, _) => - coll - .one[Report]( + for + prev <- coll.one[Report]: $doc( "user" -> candidate.suspect.user.id, "reason" -> candidate.reason, "open" -> true ) - ) - .flatMap { prev => - val report = Report.make(scored, prev) - lila.mon.mod.report.create(report.reason.key, scored.score.value.toInt).increment() - if report.isRecentComm && - report.score.value >= thresholds.discord() && - prev.exists(_.score.value < thresholds.discord()) - then ircApi.commReportBurst(c.suspect.user.light) - coll.update.one($id(report.id), report, upsert = true).void >> - autoAnalysis(candidate).andDo: - if report.isCheat then - Bus.publish(lila.core.report.CheatReportCreated(report.user), "cheatReport") - } - .andDo(maxScoreCache.invalidateUnit()) + report = Report.make(scored, prev) + _ = lila.mon.mod.report.create(report.reason.key, scored.score.value.toInt).increment() + _ = if report.isRecentComm && + report.score.value >= thresholds.discord() && + prev.exists(_.score.value < thresholds.discord()) + then ircApi.commReportBurst(c.suspect.user.light) + _ <- coll.update.one($id(report.id), report, upsert = true) + _ <- autoAnalysis(candidate) + yield + if report.isCheat then Bus.publish(lila.core.report.CheatReportCreated(report.user), "cheatReport") + maxScoreCache.invalidateUnit() } } - def commFlag(reporter: Reporter, suspect: Suspect, resource: String, text: String) = - create( - Candidate( - reporter, - suspect, - Reason.Comm, - s"${Reason.Comm.flagText} $resource ${text.take(140)}" - ) + def commFlag(reporter: Reporter, suspect: Suspect, resource: String, text: String) = create: + Candidate( + reporter, + suspect, + Reason.Comm, + s"${Reason.flagText} $resource ${text.take(140)}" ) def autoCommFlag(suspectId: SuspectId, resource: String, text: String, critical: Boolean = false) = @@ -94,7 +89,7 @@ final class ReportApi( reporter, suspect, Reason.Comm, - s"${Reason.Comm.flagText} $resource ${text.take(140)}" + s"${Reason.flagText} $resource ${text.take(140)}" ), score = (_: Report.Score).map(_ * (if critical then 2 else 1)) ) diff --git a/modules/report/src/main/ReportForm.scala b/modules/report/src/main/ReportForm.scala index d511dcd7c3c49..11717e68b1d54 100644 --- a/modules/report/src/main/ReportForm.scala +++ b/modules/report/src/main/ReportForm.scala @@ -7,6 +7,7 @@ import play.api.data.validation.* import lila.core.config.NetDomain import lila.core.LightUser import lila.core.report.SuspectId +import lila.common.Form.cleanNonEmptyText final private[report] class ReportForm( lightUserAsync: LightUser.Getter, @@ -30,7 +31,7 @@ final private[report] class ReportForm( u => !UserId.isOfficial(u) ), "reason" -> text.verifying("error.required", Reason.keys contains _), - "text" -> text(minLength = 5, maxLength = 2000) + "text" -> cleanNonEmptyText(minLength = 5) ) { (username, reason, text) => ReportSetup( user = blockingFetchUser(username).err("Unknown username " + username), @@ -39,12 +40,13 @@ final private[report] class ReportForm( ) }(_.values.some) .verifying(cheatLinkConstraint) + .verifying(s"Maximum report length is 3000 characters", _.text.length <= 3000) val flag = Form: mapping( "username" -> lila.common.Form.username.historicalField, "resource" -> nonEmptyText, - "text" -> text(minLength = 3, maxLength = 140) + "text" -> text(minLength = 1, maxLength = 140) )(ReportFlag.apply)(unapply) private def blockingFetchUser(username: UserStr) = diff --git a/modules/report/src/main/ReportScore.scala b/modules/report/src/main/ReportScore.scala index a318d10236bb3..807b270ce6e4f 100644 --- a/modules/report/src/main/ReportScore.scala +++ b/modules/report/src/main/ReportScore.scala @@ -39,7 +39,8 @@ final private class ReportScore( else if c.isIrwinCheat then 45d else if c.isKaladinCheat then 25d else if c.isPrint || c.isCoachReview || c.is(_.Playbans) then baseScore * 2 - else if c.is(_.Username) || c.is(_.Sexism) then score + 30 + else if c.is(_.Violence) || c.is(_.Harass) || c.is(_.SelfHarm) || c.is(_.Hate) then 50d + else if c.is(_.Username) then 50d else score private val gameRegex = ReportForm.gameLinkRegex(domain) diff --git a/modules/report/src/main/Room.scala b/modules/report/src/main/Room.scala index 381e18cff88ab..ff6f0b97fd925 100644 --- a/modules/report/src/main/Room.scala +++ b/modules/report/src/main/Room.scala @@ -24,11 +24,11 @@ object Room: def apply(reason: Reason): Room = import lila.report.{ Reason as R } reason match - case R.Cheat => Cheat - case R.Boost => Boost - case R.AltPrint | R.CheatPrint => Print - case R.Comm | R.Sexism => Comm - case R.Other | R.Playbans | R.Username => Other + case R.Cheat => Cheat + case R.Boost => Boost + case R.AltPrint => Print + case r if r.isComm => Comm + case _ => Other case class Scores(value: Map[Room, Int]): def get = value.get diff --git a/modules/report/src/main/ui/ReportUi.scala b/modules/report/src/main/ui/ReportUi.scala index cdda91268f2c5..c18d0e6b3c7ae 100644 --- a/modules/report/src/main/ui/ReportUi.scala +++ b/modules/report/src/main/ui/ReportUi.scala @@ -4,25 +4,118 @@ package ui import lila.ui.* import ScalatagsTemplate.{ *, given } import lila.core.i18n.{ Translate, I18nKey as trans } +import play.api.data.Form object ReportUi: - def translatedReasonChoices(using Translate) = - List( - (Reason.Cheat.key, trans.site.cheat.txt()), - (Reason.Comm.key, trans.site.insult.txt()), - (Reason.Boost.key, trans.site.ratingManipulation.txt()), - (Reason.Comm.key, trans.site.troll.txt()), - (Reason.Sexism.key, "Sexual harassment or Sexist remarks"), - (Reason.Username.key, trans.site.username.txt()), - (Reason.Other.key, trans.site.other.txt()) - ) - def reportScore(score: Report.Score): Frag = span(cls := s"score ${score.color}")(score.value.toInt) final class ReportUi(helpers: Helpers): import helpers.{ given, * } + import ReportUi.* + + def form(form: Form[?], reqUser: Option[User] = None)(using ctx: Context) = + Page(trans.site.reportAUser.txt()) + .css("bits.form3") + .js( + embedJsUnsafeLoadThen( + """$('#form3-reason').on('change', function() { + $('.report-reason').addClass('none').filter('.report-reason-' + this.value).removeClass('none'); + })""" + ) + ): + val defaultReason = form("reason").value.orElse(translatedReasonChoices.headOption.map(_._1.key)) + main(cls := "page-small box box-pad report")( + h1(cls := "box__top")(trans.site.reportAUser()), + postForm( + cls := "form3", + action := s"${routes.Report.create}${reqUser.so(u => "?username=" + u.username)}" + )( + div(cls := "form-group")( + p( + a( + href := routes.Cms.lonePage(lila.core.id.CmsPageKey("report-faq")), + dataIcon := Icon.InfoCircle, + cls := "text" + ): + "Read more about Lichess reports" + ), + ctx.req.queryString + .contains("postUrl") + .option( + p( + "Here for DMCA or Intellectual Property Take Down Notice? ", + a(href := "/dmca")("Complete this form instead"), + "." + ) + ) + ), + form3.globalError(form), + form3.group(form("username"), trans.site.user(), klass = "field_to complete-parent"): f => + reqUser + .map: user => + frag(userLink(user), form3.hidden(f, user.id.value.some)) + .getOrElse: + div(form3.input(f, klass = "user-autocomplete")(dataTag := "span", autofocus)) + , + if ctx.req.queryString contains "reason" + then form3.hidden(form("reason")) + else + form3.group(form("reason"), trans.site.reason()): f => + form3.select( + f, + translatedReasonChoices.map((r, t) => (r.key, t)), + trans.site.whatIsIheMatter.txt().some + ) + , + form3.group(form("text"), trans.site.description(), help = descriptionHelp(~defaultReason).some): + form3.textarea(_)(rows := 8) + , + form3.actions( + a(href := routes.Lobby.home)(trans.site.cancel()), + form3.submit(trans.site.send()) + ) + ) + ) + + private def descriptionHelp(current: String)(using ctx: Context) = frag: + import Reason.* + val englishPlease = "Your report will be processed faster if written in English." + val maxLength = "Maximum 3000 characters." + translatedReasonChoices + .map(_._1) + .distinct + .map: reason => + span( + cls := List(s"report-reason report-reason-${reason.key}" -> true, "none" -> (current != reason.key)) + )( + if reason == Cheat || reason == Boost then trans.site.reportDescriptionHelp() + else if reason == Username then "Please explain briefly what about this username is offensive." + else + "Please provide as much information as possible, including relevant game links, posts, and messages." + , + " ", + englishPlease, + " ", + maxLength + ) + + private def translatedReasonChoices(using Translate) = + import Reason.* + List( + (Cheat, trans.site.cheat.txt()), + (Stall, "Stalling / Leaving Games"), + (Boost, "Sandbagging / Boosting / Match fixing"), + (VerbalAbuse, "Verbal abuse / Cursing / Trolling"), + (Violence, "Violence / Threats"), + (Harass, "Harassment / Bullying / Stalking"), + (SelfHarm, "Suicide / Self-Injury"), + (Hate, "Hate Speech / Sexism"), + (Spam, "Spamming"), + (Username, trans.site.username.txt()), + (Other, trans.site.other.txt()) + ) def thanks(userId: UserId, blocked: Boolean)(using ctx: Context) = val title = "Thanks for the report" diff --git a/modules/round/src/main/BotFarming.scala b/modules/round/src/main/BotFarming.scala deleted file mode 100644 index 808a96891325e..0000000000000 --- a/modules/round/src/main/BotFarming.scala +++ /dev/null @@ -1,30 +0,0 @@ -package lila.round - -import lila.core.LightUser.IsBotSync -import lila.game.{ CrosstableApi, Game, GameRepo } - -final private class BotFarming( - gameRepo: GameRepo, - crosstableApi: CrosstableApi, - isBotSync: IsBotSync -)(using Executor): - - val SAME_PLIES = 20 - val PREV_GAMES = 2 - - /* true if - * - at least one bot - * - rated - * - recent game in same matchup has same first SAME_PLIES and same winner - */ - def apply(g: Game): Fu[Boolean] = - g.twoUserIds match - case Some((u1, u2)) if g.finished && g.rated && g.userIds.exists(isBotSync) => - crosstableApi(u1, u2).flatMap { ct => - gameRepo.gamesFromSecondary(ct.results.reverse.take(PREV_GAMES).map(_.gameId)).map { - _.exists: prev => - g.winnerUserId === prev.winnerUserId && - g.sans.take(SAME_PLIES) === prev.sans.take(SAME_PLIES) - } - } - case _ => fuccess(false) diff --git a/modules/round/src/main/Env.scala b/modules/round/src/main/Env.scala index d6dd2323425b3..9cc6e0f3697d5 100644 --- a/modules/round/src/main/Env.scala +++ b/modules/round/src/main/Env.scala @@ -166,7 +166,7 @@ final class Env( lazy val recentTvGames = wire[RecentTvGames] - private lazy val botFarming = wire[BotFarming] + private lazy val farmBoostDetection = wire[FarmBoostDetection] lazy val perfsUpdater: PerfsUpdater = wire[PerfsUpdater] diff --git a/modules/round/src/main/FarmBoostDetection.scala b/modules/round/src/main/FarmBoostDetection.scala new file mode 100644 index 0000000000000..06e1f0e03c2ee --- /dev/null +++ b/modules/round/src/main/FarmBoostDetection.scala @@ -0,0 +1,42 @@ +package lila.round + +import lila.core.LightUser.IsBotSync +import lila.game.{ CrosstableApi, Game, GameRepo } +import chess.ByColor +import lila.core.perf.UserWithPerfs + +final private class FarmBoostDetection( + gameRepo: GameRepo, + crosstableApi: CrosstableApi, + isBotSync: IsBotSync +)(using Executor): + + val SAME_PLIES = 20 + val PREV_GAMES = 2 + + /* true if + * - at least one bot + * - rated + * - recent game in same matchup has same first SAME_PLIES and same winner + */ + def botFarming(g: Game): Fu[Boolean] = + g.twoUserIds match + case Some((u1, u2)) if g.finished && g.rated && g.userIds.exists(isBotSync) => + crosstableApi(u1, u2).flatMap: ct => + gameRepo + .gamesFromSecondary(ct.results.reverse.take(PREV_GAMES).map(_.gameId)) + .map: + _.exists: prev => + g.winnerUserId === prev.winnerUserId && + g.sans.take(SAME_PLIES) === prev.sans.take(SAME_PLIES) + .addEffect: + if _ then lila.mon.round.farming.bot.increment() + case _ => fuccess(false) + + def newAccountBoosting(g: Game, users: ByColor[UserWithPerfs]): Boolean = + val found = g.sourceIs(_.Friend) && + g.playedTurns < 20 && + g.durationSeconds.exists(_ < 60) && + users.exists(_.perfs(g.perfKey).provisional.yes) + if found then lila.mon.round.farming.provisional.increment() + found diff --git a/modules/round/src/main/PerfsUpdater.scala b/modules/round/src/main/PerfsUpdater.scala index dc54253b36432..e8eb66b822dcb 100644 --- a/modules/round/src/main/PerfsUpdater.scala +++ b/modules/round/src/main/PerfsUpdater.scala @@ -14,7 +14,7 @@ final class PerfsUpdater( gameRepo: lila.game.GameRepo, userApi: UserApi, rankingApi: RankingApi, - botFarming: BotFarming, + farming: FarmBoostDetection, ratingFactors: () => RatingFactors )(using Executor): @@ -22,8 +22,9 @@ final class PerfsUpdater( // returns rating diffs def save(game: Game, white: UserWithPerfs, black: UserWithPerfs): Fu[Option[ByColor[IntRatingDiff]]] = - botFarming(game).flatMap { + farming.botFarming(game).flatMap { if _ then fuccess(none) + else if farming.newAccountBoosting(game, ByColor(white, black)) then fuccess(none) else val ratingPerf: Option[PerfKey] = if game.variant.fromPosition diff --git a/modules/security/src/main/GeoIP.scala b/modules/security/src/main/GeoIP.scala index ac37985eec7ca..2026c1421c80d 100644 --- a/modules/security/src/main/GeoIP.scala +++ b/modules/security/src/main/GeoIP.scala @@ -73,6 +73,7 @@ object Location: def isSuspicious(loc: Location) = loc == unknown || - loc.region.has("Kirov Oblast") + loc.region.has("Kirov Oblast") || + loc.region.has("Samsun") case class WithProxy(location: Location, proxy: IsProxy) diff --git a/modules/security/src/main/Signup.scala b/modules/security/src/main/Signup.scala index cb803b2306233..62ff30f89d6d4 100644 --- a/modules/security/src/main/Signup.scala +++ b/modules/security/src/main/Signup.scala @@ -71,25 +71,19 @@ final class Signup( , data => for - suspIp <- ipTrust.isSuspicious(ip) - ipData <- ipTrust.data(ip) - result <- hcaptcha.verify().flatMap { + suspIp <- ipTrust.isSuspicious(ip) + ipData <- ipTrust.data(ip) + captcha <- hcaptcha.verify() + result <- captcha match case Hcaptcha.Result.Fail => fuccess(Signup.Result.MissingCaptcha) - case hcaptchaResult => + case _ => signupRateLimit( data.username.id, suspIp = suspIp, - captched = hcaptchaResult == Hcaptcha.Result.Valid + captched = captcha == Hcaptcha.Result.Valid ): MustConfirmEmail(data.fingerPrint, data.email, suspIp = suspIp).flatMap { mustConfirm => - monitor( - data, - hcaptchaResult, - mustConfirm, - ipData, - ipSusp = suspIp, - api = none - ) + monitor(data, captcha, mustConfirm, ipData, ipSusp = suspIp, api = none) lila.mon.user.register.mustConfirmEmail(mustConfirm.toString).increment() val passwordHash = authenticator.passEnc(data.clearPassword) userRepo @@ -103,11 +97,10 @@ final class Signup( ) .orFail(s"No user could be created for ${data.username}") .addEffect: - logSignup(req, _, data.email, data.fingerPrint, none, mustConfirm) + logSignup(req, _, data.email, data.fingerPrint, none, captcha, mustConfirm) .flatMap: confirmOrAllSet(data.email, mustConfirm, data.fingerPrint, none) } - } yield result ) } @@ -167,7 +160,7 @@ final class Signup( ) .orFail(s"No user could be created for ${data.username}") .addEffect: - logSignup(req, _, data.email, none, apiVersion.some, mustConfirm) + logSignup(req, _, data.email, none, apiVersion.some, Hcaptcha.Result.Mobile, mustConfirm) .flatMap: confirmOrAllSet(data.email, mustConfirm, none, apiVersion.some) yield result @@ -223,13 +216,14 @@ final class Signup( email: EmailAddress, fingerPrint: Option[FingerPrint], apiVersion: Option[ApiVersion], + captcha: Hcaptcha.Result, mustConfirm: MustConfirmEmail ) = disposableEmailAttempt.onSuccess(user, email, HTTPRequest.ipAddress(req)) authLog( user.username.into(UserStr), email.value, - s"fp: $fingerPrint mustConfirm: $mustConfirm fp: ${fingerPrint + s"fp: $fingerPrint mustConfirm: $mustConfirm captcha: $captcha fp: ${fingerPrint .so(_.value)} ip: ${HTTPRequest.ipAddress(req)} api: $apiVersion" ) diff --git a/modules/security/src/main/VerifyMail.scala b/modules/security/src/main/VerifyMail.scala index 238c974b505f5..d40b3e42b458c 100644 --- a/modules/security/src/main/VerifyMail.scala +++ b/modules/security/src/main/VerifyMail.scala @@ -53,6 +53,31 @@ final private class VerifyMail( export cache.invalidate private def fetch(domain: Domain.Lower): Fu[Boolean] = + List(fetchFree(domain), fetchPaid(domain)) + .map(_.logFailure(logger).recover(_ => true)) // fetch fail = domain ok + .parallel + .map(_.forall(identity)) // ok if both say the domain is ok + + private def fetchFree(domain: Domain.Lower): Fu[Boolean] = + val url = s"https://api.mailcheck.ai/domain/$domain" + ws.url(url) + .get() + .withTimeout(8.seconds, "mailcheck.fetch") + .map: res => + (for + js <- res.body[JsValue].asOpt[JsObject] + if res.status == 200 + disposable <- js.boolean("disposable") + yield + val ok = !disposable + logger.info: + s"Mailcheck $domain = $ok {disposable:$disposable}" + ok + ).getOrElse: + throw lila.core.lilaism.LilaException(s"$url ${res.status} ${res.body[String].take(200)}") + .monTry(res => _.security.mailcheckApi.fetch(res.isSuccess, res.getOrElse(true))) + + private def fetchPaid(domain: Domain.Lower): Fu[Boolean] = val url = s"https://verifymail.io/api/$domain" ws.url(url) .withQueryStringParameters("key" -> config.key.value) diff --git a/modules/setup/src/main/ApiConfig.scala b/modules/setup/src/main/ApiConfig.scala index 9ae05303a8525..688f4dfac46e7 100644 --- a/modules/setup/src/main/ApiConfig.scala +++ b/modules/setup/src/main/ApiConfig.scala @@ -23,6 +23,7 @@ final case class ApiConfig( ): def perfType: PerfType = lila.rating.PerfType(variant, chess.Speed(days.isEmpty.so(clock))) + def perfKey = perfType.key def validFen = Variant.isValidInitialFen(variant, position) diff --git a/modules/setup/src/main/Config.scala b/modules/setup/src/main/Config.scala index 583e2be167797..e53f2dffb40b9 100644 --- a/modules/setup/src/main/Config.scala +++ b/modules/setup/src/main/Config.scala @@ -58,6 +58,7 @@ private[setup] trait Config: def makeSpeed: Speed = chess.Speed(makeClock) def perfType: PerfType = lila.rating.PerfType(variant, makeSpeed) + def perfKey = perfType.key trait Positional: self: Config => diff --git a/modules/simul/src/main/ui/SimulFormUi.scala b/modules/simul/src/main/ui/SimulFormUi.scala index 75e1af99cd995..4f9b70330494a 100644 --- a/modules/simul/src/main/ui/SimulFormUi.scala +++ b/modules/simul/src/main/ui/SimulFormUi.scala @@ -147,11 +147,13 @@ final class SimulFormUi(helpers: Helpers)( gatheringFormUi.maxRating(form("conditions.maxRating.rating")) ) ), - form3.group( - form("estimatedStartAt"), - trans.site.estimatedStart(), - half = true - )(form3.flatpickr(_)), + form3.split( + form3.group( + form("estimatedStartAt"), + trans.site.estimatedStart(), + half = true + )(form3.flatpickr(_)) + ), form3.group( form("text"), trans.site.simulDescription(), diff --git a/modules/study/src/main/BSONHandlers.scala b/modules/study/src/main/BSONHandlers.scala index edcc7b54f735a..b627f7dbbaaec 100644 --- a/modules/study/src/main/BSONHandlers.scala +++ b/modules/study/src/main/BSONHandlers.scala @@ -327,15 +327,21 @@ object BSONHandlers: private val clockPair: BSONHandler[PairOf[Option[Centis]]] = optionPairHandler given BSONHandler[Chapter.BothClocks] = clockPair.as[Chapter.BothClocks](ByColor.fromPair, _.toPair) + given BSONHandler[Chapter.Check] = quickHandler[Chapter.Check]( + { case BSONString(v) => if v == "#" then Chapter.Check.Mate else Chapter.Check.Check }, + v => BSONString(if v == Chapter.Check.Mate then "#" else "+") + ) given BSON[Chapter.LastPosDenorm] with def reads(r: Reader) = Chapter.LastPosDenorm( fen = r.getO[Fen.Full]("fen") | Fen.initial, uci = r.getO[Uci]("uci"), + check = r.getO[Chapter.Check]("check"), clocks = ~r.getO[Chapter.BothClocks]("clocks") ) def writes(w: Writer, l: Chapter.LastPosDenorm) = $doc( "fen" -> l.fen.some.filterNot(Fen.Full.isInitial), "uci" -> l.uci, + "check" -> l.check, "clocks" -> l.clocks.some.filter(_.exists(_.isDefined)) ) diff --git a/modules/study/src/main/Chapter.scala b/modules/study/src/main/Chapter.scala index 6f0a89152dffb..a872576631424 100644 --- a/modules/study/src/main/Chapter.scala +++ b/modules/study/src/main/Chapter.scala @@ -41,7 +41,13 @@ case class Chapter( val parentNode = parentPath.flatMap(root.nodeAt) val clockSwap = ByColor(node.clock, parentNode.flatMap(_.clock).orElse(node.clock)) if node.color.black then clockSwap else clockSwap.swap - Chapter.LastPosDenorm(node.fen, node.moveOption.map(_.uci), clocks = clocks) + val uci = node.moveOption.map(_.uci) + val check = node.moveOption + .flatMap(_.san.value.lastOption) + .collect: + case '+' => Chapter.Check.Check + case '#' => Chapter.Check.Mate + Chapter.LastPosDenorm(node.fen, uci, check, clocks) copy(denorm = newDenorm) def updateRoot(f: Root => Option[Root]) = @@ -104,6 +110,7 @@ case class Chapter( fen = denorm.fold(Fen.initial)(_.fen), lastMove = denorm.flatMap(_.uci), lastMoveAt = relay.map(_.lastMoveAt), + check = denorm.flatMap(_.check), result = tags.outcome.isDefined.option(tags.outcome) ) @@ -149,9 +156,12 @@ object Chapter: type BothClocks = ByColor[Option[Centis]] + enum Check: + case Check, Mate + /* Last position of the main line. * Used for chapter previews. */ - case class LastPosDenorm(fen: Fen.Full, uci: Option[Uci], clocks: BothClocks) + case class LastPosDenorm(fen: Fen.Full, uci: Option[Uci], check: Option[Check], clocks: BothClocks) case class IdName(@Key("_id") id: StudyChapterId, name: StudyChapterName) diff --git a/modules/study/src/main/PgnDump.scala b/modules/study/src/main/PgnDump.scala index 7cecad8234f5b..f06b214152928 100644 --- a/modules/study/src/main/PgnDump.scala +++ b/modules/study/src/main/PgnDump.scala @@ -71,7 +71,7 @@ final class PgnDump( val opening = chapter.opening val genTags = List( Tag(_.Event, s"${study.name}: ${chapter.name}"), - Tag(_.Site, chapterUrl(study.id, chapter.id)), + Tag(_.Site, flags.site | chapterUrl(study.id, chapter.id)), Tag(_.Variant, chapter.setup.variant.name.capitalize), Tag(_.ECO, opening.fold("?")(_.eco)), Tag(_.Opening, opening.fold("?")(_.name)), @@ -109,9 +109,10 @@ object PgnDump: variations: Boolean, clocks: Boolean, source: Boolean, - orientation: Boolean + orientation: Boolean, + site: Option[String] ) - val fullFlags = WithFlags(true, true, true, true, true) + val fullFlags = WithFlags(true, true, true, true, true, none) def rootToPgn(root: Root, tags: Tags, comments: InitialComments)(using WithFlags): Pgn = rootToPgn(NewRoot(root), tags, comments) diff --git a/modules/study/src/main/Study.scala b/modules/study/src/main/Study.scala index 086d61735f53e..b2b779bca13db 100644 --- a/modules/study/src/main/Study.scala +++ b/modules/study/src/main/Study.scala @@ -58,7 +58,7 @@ case class Study( def isOld = (nowSeconds - updatedAt.toSeconds) > 20 * 60 def isRelay = from match - case From.Relay(_) => true + case _: From.Relay => true case _ => false def cloneFor(user: User): Study = diff --git a/modules/study/src/main/StudyApi.scala b/modules/study/src/main/StudyApi.scala index 2981e1385d6aa..9192f3041284a 100644 --- a/modules/study/src/main/StudyApi.scala +++ b/modules/study/src/main/StudyApi.scala @@ -632,8 +632,7 @@ final class StudyApi( def doAddChapter(study: Study, chapter: Chapter, sticky: Boolean, who: Who): Funit = for _ <- chapterRepo.insert(chapter) newStudy = study.withChapter(chapter) - _ <- sticky.so(studyRepo.updateSomeFields(newStudy)) - _ <- studyRepo.updateNow(study) + _ <- if sticky then studyRepo.updateSomeFields(newStudy) else studyRepo.updateNow(study) yield sendTo(study.id)(_.addChapter(newStudy.position, sticky, who)) indexStudy(study) diff --git a/modules/study/src/main/StudyChapterPreview.scala b/modules/study/src/main/StudyChapterPreview.scala index 55948613e399e..3661354a19a5a 100644 --- a/modules/study/src/main/StudyChapterPreview.scala +++ b/modules/study/src/main/StudyChapterPreview.scala @@ -17,6 +17,7 @@ case class ChapterPreview( fen: Fen.Full, lastMove: Option[Uci], lastMoveAt: Option[Instant], + check: Option[Chapter.Check], /* None = No Result PGN tag, the chapter may not be a game * Some(None) = Result PGN tag is "*", the game is ongoing * Some(Some(Outcome)) = Game is over with a result @@ -51,9 +52,21 @@ final class ChapterPreviewApi( def apply(studyId: StudyId): Fu[AsJsons] = cache.get(studyId) + def withoutInitialEmpty(studyId: StudyId): Fu[AsJsons] = + apply(studyId).map: json => + val singleInitial = json + .asOpt[JsArray] + .map(_.value) + .filter(_.sizeIs == 1) + .flatMap(_.headOption) + .exists: + case single: JsObject => single.str("name").contains("Chapter 1") + case _ => false + if singleInitial then JsArray.empty else json + object dataList: private[ChapterPreviewApi] val cache = - cacheApi[StudyId, List[ChapterPreview]](256, "study.chapterPreview.data"): + cacheApi[StudyId, List[ChapterPreview]](512, "study.chapterPreview.data"): _.expireAfterWrite(1 minute).buildAsyncFuture(listAll) def apply(studyId: StudyId): Fu[List[ChapterPreview]] = cache.get(studyId) @@ -122,6 +135,10 @@ object ChapterPreview: .add("clock" -> p.clock) .add("fed" -> p.fideId.flatMap(federations.get)) + private given Writes[Chapter.Check] = Writes: + case Chapter.Check.Check => JsString("+") + case Chapter.Check.Mate => JsString("#") + private def writesWithFederations(using Federation.ByFideIds): OWrites[ChapterPreview] = c => Json .obj( @@ -132,6 +149,7 @@ object ChapterPreview: .add("players", c.players.map(_.mapList(playerWithFederations))) .add("orientation", c.orientation.some.filter(_.black)) .add("lastMove", c.lastMove) + .add("check", c.check) .add("thinkTime", c.thinkTime) .add("status", c.result.map(o => Outcome.showResult(o).replace("1/2", "½"))) @@ -163,5 +181,6 @@ object ChapterPreview: fen = lastPos.map(_.fen).orElse(doc.getAsOpt[Fen.Full]("rootFen")).getOrElse(Fen.initial), lastMove = lastPos.flatMap(_.uci), lastMoveAt = lastMoveAt, + check = lastPos.flatMap(_.check), result = tags.flatMap(_(_.Result)).map(Outcome.fromResult) ) diff --git a/modules/study/src/main/package.scala b/modules/study/src/main/package.scala index 93ddc484696c5..e874850eaeaed 100644 --- a/modules/study/src/main/package.scala +++ b/modules/study/src/main/package.scala @@ -5,5 +5,3 @@ export lila.common.extensions.* export lila.core.study.data.{ StudyName, StudyChapterName } private val logger = lila.log("study") - -private type ChapterMap = Map[lila.study.StudyChapterId, lila.study.Chapter] diff --git a/modules/study/src/test/Helpers.scala b/modules/study/src/test/Helpers.scala index 254236ec01aa0..c3725b687981a 100644 --- a/modules/study/src/test/Helpers.scala +++ b/modules/study/src/test/Helpers.scala @@ -12,11 +12,11 @@ object Helpers: import lila.tree.NewTree.* def rootToPgn(root: Root): PgnStr = PgnDump - .rootToPgn(root, Tags.empty)(using PgnDump.WithFlags(true, true, true, true, false)) + .rootToPgn(root, Tags.empty)(using PgnDump.WithFlags(true, true, true, true, false, none)) .render def rootToPgn(root: NewRoot): PgnStr = PgnDump - .rootToPgn(root, Tags.empty)(using PgnDump.WithFlags(true, true, true, true, false)) + .rootToPgn(root, Tags.empty)(using PgnDump.WithFlags(true, true, true, true, false, none)) .render extension (root: Root) diff --git a/modules/swiss/src/main/SwissCondition.scala b/modules/swiss/src/main/SwissCondition.scala index 5ba69a418bd1e..64a44ca3381b8 100644 --- a/modules/swiss/src/main/SwissCondition.scala +++ b/modules/swiss/src/main/SwissCondition.scala @@ -45,6 +45,8 @@ object SwissCondition: def similar(other: All) = sameRatings(other) && titled == other.titled + def isDefault = this == All.empty + object All: val empty = All(none, none, none, none, none, none, PlayYourGames.some) given Zero[All] = Zero(empty) diff --git a/modules/swiss/src/main/ui/SwissFormUi.scala b/modules/swiss/src/main/ui/SwissFormUi.scala index 7268df6924bff..ff6fde274ab11 100644 --- a/modules/swiss/src/main/ui/SwissFormUi.scala +++ b/modules/swiss/src/main/ui/SwissFormUi.scala @@ -35,17 +35,11 @@ final class SwissFormUi(helpers: Helpers)( trans.site.ourEventTips() ) ), - form3.split(fields.name, fields.nbRounds), - form3.split(fields.description, fields.rated), - fields.clock, - form3.split(fields.roundInterval, fields.startsAt), - advancedSettings( - form3.split(fields.variant, fields.position), - form3.split(fields.chatFor, fields.entryCode), - condition(form), - form3.split(fields.playYourGames, fields.allowList), - form3.split(fields.forbiddenPairings, fields.manualPairings) - ), + fields.tournamentFields, + fields.gameFields, + fields.featuresFields, + fields.conditionFields, + fields.pairingsFields, form3.globalError(form), form3.actions( a(href := routes.Team.show(teamId))(trans.site.cancel()), @@ -60,23 +54,6 @@ final class SwissFormUi(helpers: Helpers)( private val gatheringFormUi = GatheringFormUi(helpers) - private def condition(form: Form[SwissForm.SwissData])(using ctx: Context) = - frag( - form3.split( - gatheringFormUi.nbRatedGame(form("conditions.nbRatedGame.nb")), - gatheringFormUi.accountAge(form("conditions.accountAge")) - ), - (ctx.me.exists(_.hasTitle) || Granter.opt(_.ManageTournament)).option { - form3.split( - gatheringFormUi.titled(form("conditions.titled")) - ) - }, - form3.split( - gatheringFormUi.minRating(form("conditions.minRating.rating")), - gatheringFormUi.maxRating(form("conditions.maxRating.rating")) - ) - ) - def edit(swiss: Swiss, form: Form[SwissForm.SwissData])(using Context) = Page(swiss.name) .css("swiss.form") @@ -86,17 +63,11 @@ final class SwissFormUi(helpers: Helpers)( div(cls := "swiss__form box box-pad")( h1(cls := "box__top")("Edit ", swiss.name), postForm(cls := "form3", action := routes.Swiss.update(swiss.id))( - form3.split(fields.name, fields.nbRounds), - form3.split(fields.description, fields.rated), - fields.clock, - form3.split(fields.roundInterval, swiss.isCreated.option(fields.startsAt)), - advancedSettings( - form3.split(fields.variant, fields.position), - form3.split(fields.chatFor, fields.entryCode), - condition(form), - form3.split(fields.playYourGames, fields.allowList), - form3.split(fields.forbiddenPairings, fields.manualPairings) - ), + fields.tournamentFields, + fields.gameFields, + fields.featuresFields, + fields.conditionFields, + fields.pairingsFields, form3.globalError(form), form3.actions( a(href := routes.Swiss.show(swiss.id))(trans.site.cancel()), @@ -115,6 +86,18 @@ final class SwissFormUi(helpers: Helpers)( private def disabledAfterStart = swiss.exists(!_.isCreated) + def tournamentFields = + form3.fieldset("Tournament", toggle = true.some)( + form3.split(name, nbRounds), + form3.split(startsAt, description) + ) + + def gameFields = + form3.fieldset("Games", toggle = true.some)( + clock, + form3.split(variant, position) + ) + def name = form3.group(form("name"), trans.site.name()) { f => div( @@ -137,17 +120,6 @@ final class SwissFormUi(helpers: Helpers)( )( form3.input(_, typ = "number") ) - - def rated = - frag( - form3.checkbox( - form("rated"), - trans.site.rated(), - help = trans.site.ratedFormHelp().some, - half = true - ), - form3.hidden(form("rated"), "false".some) // hack allow disabling rated - ) def variant = form3.group(form("variant"), trans.site.variant(), half = true)( form3.select( @@ -165,10 +137,6 @@ final class SwissFormUi(helpers: Helpers)( form3.select(_, GatheringClock.incrementChoices, disabled = disabledAfterStart) ) ) - def roundInterval = - form3.group(form("roundInterval"), trans.swiss.roundInterval(), half = true)( - form3.select(_, SwissForm.roundIntervalChoices) - ) def description = form3.group( form("description"), @@ -192,28 +160,67 @@ final class SwissFormUi(helpers: Helpers)( trans.swiss.tournStartDate(), help = trans.site.inYourLocalTimezone().some, half = true - )(form3.flatpickr(_)) + )(form3.flatpickr(_)(swiss.exists(!_.isCreated).option(disabled))) - def chatFor = - form3.group(form("chatFor"), trans.site.tournChat(), half = true) { f => - form3.select( - f, - Seq( - Swiss.ChatFor.NONE -> trans.site.noChat.txt(), - Swiss.ChatFor.LEADERS -> trans.site.onlyTeamLeaders.txt(), - Swiss.ChatFor.MEMBERS -> trans.site.onlyTeamMembers.txt(), - Swiss.ChatFor.ALL -> trans.study.everyone.txt() + def conditionFields = + form3.fieldset("Entry conditions", toggle = swiss.exists(!_.settings.conditions.isDefault).some)( + form3.split( + gatheringFormUi.nbRatedGame(form("conditions.nbRatedGame.nb")), + gatheringFormUi.accountAge(form("conditions.accountAge")) + ), + form3.split( + gatheringFormUi.minRating(form("conditions.minRating.rating")), + gatheringFormUi.maxRating(form("conditions.maxRating.rating")) + ), + form3.split( + playYourGames, + (summon[Context].me.exists(_.hasTitle) || Granter.opt(_.ManageTournament)).option: + gatheringFormUi.titled(form("conditions.titled")) + ), + form3.split( + form3.group( + form("password"), + trans.site.tournamentEntryCode(), + help = trans.site.makePrivateTournament().some, + half = true + )(form3.input(_)(autocomplete := "off")), + allowList + ) + ) + + def featuresFields = + form3.fieldset("Features", toggle = false.some)( + form3.split( + form3.group(form("chatFor"), trans.site.tournChat(), half = true) { f => + form3.select( + f, + Seq( + Swiss.ChatFor.NONE -> trans.site.noChat.txt(), + Swiss.ChatFor.LEADERS -> trans.site.onlyTeamLeaders.txt(), + Swiss.ChatFor.MEMBERS -> trans.site.onlyTeamMembers.txt(), + Swiss.ChatFor.ALL -> trans.study.everyone.txt() + ) + ) + }, + form3.group(form("roundInterval"), trans.swiss.roundInterval(), half = true)( + form3.select(_, SwissForm.roundIntervalChoices) ) + ), + form3.split( + form3.checkbox( + form("rated"), + trans.site.rated(), + help = trans.site.ratedFormHelp().some, + half = true + ), + form3.hidden(form("rated"), "false".some) // hack allow disabling rated ) - } + ) - def entryCode = - form3.group( - form("password"), - trans.site.tournamentEntryCode(), - help = trans.site.makePrivateTournament().some, - half = true - )(form3.input(_)(autocomplete := "off")) + def pairingsFields = + form3.fieldset("Custom pairings", toggle = false.some)( + form3.split(forbiddenPairings, manualPairings) + ) def forbiddenPairings = form3.group( diff --git a/modules/team/src/main/TeamApi.scala b/modules/team/src/main/TeamApi.scala index e8a1720cf72bc..68999a31ebfcf 100644 --- a/modules/team/src/main/TeamApi.scala +++ b/modules/team/src/main/TeamApi.scala @@ -326,7 +326,7 @@ final class TeamApi( users <- memberRepo.userIdsByTeam(team.id) _ = users.foreach(cached.invalidateTeamIds) _ <- requestRepo.removeByTeam(team.id) - yield publish(TeamDisable(team.id)) + yield () else teamRepo .enable(team) @@ -353,7 +353,6 @@ final class TeamApi( (teamRepo.coll.delete.one($id(team.id)) >> memberRepo.removeByTeam(team.id)).andDo { logger.info(s"delete team ${team.id} by @${by.id}: $explain") - publish(TeamDelete(team.id)) } def syncBelongsTo(teamId: TeamId, userId: UserId): Boolean = diff --git a/modules/team/src/main/TeamMemberStream.scala b/modules/team/src/main/TeamMemberStream.scala index 6e465e95a510f..e573656a6427e 100644 --- a/modules/team/src/main/TeamMemberStream.scala +++ b/modules/team/src/main/TeamMemberStream.scala @@ -5,18 +5,23 @@ import reactivemongo.akkastream.cursorProducer import lila.db.dsl.{ *, given } import lila.core.perf.UserWithPerfs +import lila.core.LightUser final class TeamMemberStream( memberRepo: TeamMemberRepo, - userApi: lila.core.user.UserApi + userApi: lila.core.user.UserApi, + lightApi: lila.core.user.LightUserApi )(using Executor, akka.stream.Materializer): - def apply(team: Team, perSecond: MaxPerSecond): Source[(UserWithPerfs, Instant), ?] = - idsBatches(team, perSecond) + def apply(team: Team, fullUser: Boolean): Source[(UserWithPerfs | LightUser, Instant), ?] = + idsBatches(team, MaxPerSecond(if fullUser then 20 else 50)) + .limit(if fullUser then 1000 else 5000) .mapAsync(1): members => - userApi - .listWithPerfs(members.view.map(_._1).toList) - .map(_.zip(members.map(_._2))) + val users = + if fullUser + then userApi.listWithPerfs(members.view.map(_._1).toList) + else lightApi.asyncManyFallback(members.view.map(_._1).toList) + users.map(_.zip(members.map(_._2))) .mapConcat(identity) def subscribedIds(team: Team, perSecond: MaxPerSecond): Source[UserId, ?] = diff --git a/modules/team/src/main/ui/RequestUi.scala b/modules/team/src/main/ui/RequestUi.scala index 1b511ca2c8dd5..28f00b11f4a62 100644 --- a/modules/team/src/main/ui/RequestUi.scala +++ b/modules/team/src/main/ui/RequestUi.scala @@ -27,7 +27,7 @@ final class RequestUi(helpers: Helpers, bits: TeamUi): ) ), t.password.nonEmpty.so( - form3.passwordModified(form("password"), trt.entryCode())( + form3.passwordModified(form("password"), trt.entryCode(), reveal = false)( autocomplete := "new-password" ) ), diff --git a/modules/teamSearch/src/main/Env.scala b/modules/teamSearch/src/main/Env.scala index aded2a9415838..306435a7f4137 100644 --- a/modules/teamSearch/src/main/Env.scala +++ b/modules/teamSearch/src/main/Env.scala @@ -1,6 +1,5 @@ package lila.teamSearch -import akka.actor.* import com.softwaremill.macwire.* import scalalib.paginator.Paginator @@ -8,10 +7,7 @@ import lila.core.config.ConfigName import lila.search.client.SearchClient import lila.search.spec.Query -final class Env( - client: SearchClient, - teamApi: lila.core.team.TeamApi -)(using Executor, akka.stream.Materializer): +final class Env(client: SearchClient)(using Executor): private val maxPerPage = MaxPerPage(15) @@ -20,14 +16,3 @@ final class Env( lazy val api: TeamSearchApi = wire[TeamSearchApi] def apply(text: String, page: Int): Fu[Paginator[TeamId]] = paginatorBuilder(Query.team(text), page) - - def cli: lila.common.Cli = new: - def process = { case "team" :: "search" :: "reset" :: Nil => - api.reset.inject("done") - } - - lila.common.Bus.subscribeFun("team"): - case lila.core.team.TeamCreate(team) => api.store(team) - case lila.core.team.TeamUpdate(team, _) => api.store(team) - case lila.core.team.TeamDelete(id) => client.deleteById(index, id.value) - case lila.core.team.TeamDisable(id) => client.deleteById(index, id.value) diff --git a/modules/teamSearch/src/main/TeamSearchApi.scala b/modules/teamSearch/src/main/TeamSearchApi.scala index bd7a7c95c14c0..a79ba1d6dc735 100644 --- a/modules/teamSearch/src/main/TeamSearchApi.scala +++ b/modules/teamSearch/src/main/TeamSearchApi.scala @@ -1,45 +1,14 @@ package lila.teamSearch -import akka.stream.scaladsl.* - -import lila.core.team.TeamData import lila.search.* import lila.search.client.SearchClient -import lila.search.spec.{ Query, TeamSource } +import lila.search.spec.Query -final class TeamSearchApi( - client: SearchClient, - teamApi: lila.core.team.TeamApi -)(using Executor, akka.stream.Materializer) - extends SearchReadApi[TeamId, Query.Team]: +final class TeamSearchApi(client: SearchClient)(using Executor) extends SearchReadApi[TeamId, Query.Team]: def search(query: Query.Team, from: From, size: Size) = client .search(query, from, size) - .map: res => - res.hitIds.map(TeamId.apply) + .map(_.hitIds.map(TeamId.apply)) def count(query: Query.Team) = client.count(query).dmap(_.count) - - def store(team: TeamData) = client.storeTeam(team.id.value, toDoc(team)) - - private def toDoc(team: TeamData) = - TeamSource( - name = team.name, - description = team.description.value.take(10000), - nbMembers = team.nbMembers - ) - - def reset = - client.mapping(index) >> { - - logger.info(s"Index to ${index}") - - teamApi.cursor - .documentSource() - .via(lila.common.LilaStream.logRate[TeamData]("team index")(logger)) - .map(t => t.id.value -> toDoc(t)) - .grouped(200) - .mapAsync(1)(xs => client.storeBulkTeam(xs.toList)) - .runWith(Sink.ignore) - } >> client.refresh(index) diff --git a/modules/teamSearch/src/main/package.scala b/modules/teamSearch/src/main/package.scala index 9e3f5b1de4cfb..ac5f2552b6d3c 100644 --- a/modules/teamSearch/src/main/package.scala +++ b/modules/teamSearch/src/main/package.scala @@ -1,7 +1,6 @@ package lila.teamSearch export lila.core.lilaism.Lilaism.{ *, given } -export lila.common.extensions.* private val logger = lila.log("teamSearch") diff --git a/modules/title/src/main/TitleApi.scala b/modules/title/src/main/TitleApi.scala index b23c6ed0e39b7..bf6ce36adb4f4 100644 --- a/modules/title/src/main/TitleApi.scala +++ b/modules/title/src/main/TitleApi.scala @@ -39,7 +39,10 @@ final class TitleApi(coll: Coll, picfitApi: PicfitApi)(using Executor, BaseUrl): coll .byId[TitleRequest](id) .map: - _.filter(_.userId.is(me) || Granter(_.SetTitle)) + _.filter(_.userId.is(me) || Granter(_.TitleRequest)) + + def allOf(u: User): Fu[List[TitleRequest]] = + coll.list[TitleRequest]($doc("userId" -> u.id)) def findSimilar(req: TitleRequest): Fu[List[TitleRequest]] = val search = List( diff --git a/modules/title/src/main/ui/TitleUi.scala b/modules/title/src/main/ui/TitleUi.scala index 8a87136c5479b..03dbb08483691 100644 --- a/modules/title/src/main/ui/TitleUi.scala +++ b/modules/title/src/main/ui/TitleUi.scala @@ -62,14 +62,20 @@ final class TitleUi(helpers: Helpers)(picfitUrl: lila.core.misc.PicfitUrl): req, "idDocument", name = "Identity document", - help = frag("ID card, passport or driver's license.") + help = div( + p("ID card, passport or driver's license."), + p(trans.streamer.maxSize(s"${lila.memo.PicfitApi.uploadMaxMb}MB.")) + ) ), imageByTag( req, "selfie", name = "Your picture", - help = frag( - """A picture of yourself holding up a piece of paper, with today's date and your Lichess username written on it.""" + help = div( + p("""A picture of yourself holding up a piece of paper, with the required text:"""), + pre("""Official Lichess verification +My Lichess account is [your username] +Today's date is [current date]""") ) ) ), @@ -154,9 +160,9 @@ final class TitleUi(helpers: Helpers)(picfitUrl: lila.core.misc.PicfitUrl): if form("public").value.isDefined || form.hasErrors then form("public") else form("public").copy(value = "true".some), - frag("Publish my title"), + frag("Public account"), help = frag( - "Recommended. Your title is visible on your profile page. Your real name is NOT published." + "Makes your real name public. Required for coaching, streaming, and prize tournaments." ).some, half = true ), @@ -187,10 +193,7 @@ final class TitleUi(helpers: Helpers)(picfitUrl: lila.core.misc.PicfitUrl): cls := List("drop-target" -> true, "user-image" -> image.isDefined), attr("draggable") := "true" ), - div( - help, - p(trans.streamer.maxSize(s"${lila.memo.PicfitApi.uploadMaxMb}MB.")) - ) + help ) object thumbnail: diff --git a/modules/tournament/src/main/ui/TournamentForm.scala b/modules/tournament/src/main/ui/TournamentForm.scala index 538f9cae12940..cff2e2fe5f718 100644 --- a/modules/tournament/src/main/ui/TournamentForm.scala +++ b/modules/tournament/src/main/ui/TournamentForm.scala @@ -32,7 +32,7 @@ final class TournamentForm(val helpers: Helpers, showUi: TournamentShow)( given prefix: FormPrefix = FormPrefix.empty val fields = tourFields(form, none) Page(trans.site.newTournament.txt()) - .css("tournament.form") + .css("tournament.form", "bits.page") .js(EsmInit("bits.tourForm")): main(cls := "page-small")( div(cls := "tour__form box box-pad")( @@ -55,7 +55,7 @@ final class TournamentForm(val helpers: Helpers, showUi: TournamentShow)( ) ) ), - div(cls := "box box-pad tour__faq")(showUi.faq.pageContent) + div(cls := "box box-pad tour__faq page")(div(cls := "body")(showUi.faq())) ) def edit(tour: Tournament, form: Form[?], myTeams: List[LightTeam])(using Context) = @@ -89,40 +89,35 @@ final class TournamentForm(val helpers: Helpers, showUi: TournamentShow)( val fields = tourFields(form, none) frag( form3.globalError(form), - fields.name, - form3.split(fields.rated, fields.variant), - fields.clock, - form3.split(fields.minutes, fields.waitMinutes), - form3.split(fields.description(true), fields.startPosition), - form3.fieldset(trans.site.advancedSettings())(cls := "conditions")( - fields.advancedSettings, - div(cls := "form")( - conditionFields(form, fields, teams = leaderTeams, tour = none), - fields.startDate - ) + form3.fieldset("Tournament", toggle = true.some)( + form3.split(fields.name, fields.minutes), + form3.split(fields.description) ), + form3.fieldset("Games", toggle = true.some)( + fields.clock, + form3.split(fields.variant, fields.startPosition) + ), + fields.waitStart, + conditionFields(form, fields, teams = leaderTeams, tour = none), + featuresFields(form), fields.isTeamBattle.option(form3.hidden(form.prefix("teamBattleByTeam"))) ) def setupEdit(tour: Tournament, form: Form[?], myTeams: List[LightTeam])(using Context, FormPrefix) = val fields = tourFields(form, tour.some) frag( - form3.split(fields.name, tour.isCreated.option(fields.startDate)), - form3.split(fields.rated, fields.variant), - fields.clock, - form3.split( - if lila.tournament.TournamentForm.minutes contains tour.minutes then form3.split(fields.minutes) - else - form3.group(form.prefix("minutes"), trans.site.duration(), half = true): - form3.input(_)(tpe := "number") - ), - form3.split(fields.description(true), fields.startPosition), form3.globalError(form), - form3.fieldset(trans.site.advancedSettings())(cls := "conditions")( - fields.advancedSettings, - div(cls := "form"): - conditionFields(form, fields, teams = myTeams, tour = tour.some) - ) + form3.fieldset("Tournament", toggle = true.some)( + form3.split(fields.name, fields.minutes), + form3.split(fields.description) + ), + form3.fieldset("Games", toggle = false.some)( + fields.clock, + form3.split(fields.variant, fields.startPosition) + ), + fields.waitStart, + conditionFields(form, fields, teams = myTeams, tour = tour.some), + featuresFields(form) ) private val gatheringFormUi = GatheringFormUi(helpers) @@ -133,7 +128,8 @@ final class TournamentForm(val helpers: Helpers, showUi: TournamentShow)( teams: List[LightTeam], tour: Option[Tournament] )(using ctx: Context)(using FormPrefix) = - frag( + form3.fieldset("Entry conditions", toggle = tour.exists(_.conditions.list.nonEmpty).some)( + errMsg(form.prefix("conditions")), form3.split( fields.entryCode, (tour.isEmpty && teams.nonEmpty).option { @@ -156,35 +152,45 @@ final class TournamentForm(val helpers: Helpers, showUi: TournamentShow)( gatheringFormUi.maxRating(form.prefix("conditions.maxRating.rating")) ), form3.split( - gatheringFormUi.allowList(form.prefix("conditions.allowList")) - ), - form3.split( + gatheringFormUi.allowList(form.prefix("conditions.allowList")), (ctx.me.exists(_.hasTitle) || Granter.opt(_.ManageTournament)).so { gatheringFormUi.titled(form.prefix("conditions.titled")) - }, + } + ) + ) + + def featuresFields(form: Form[?])(using ctx: Context)(using FormPrefix) = + form3.fieldset("Features", toggle = false.some)( + form3.split( form3.checkbox( form.prefix("berserkable"), trans.arena.allowBerserk(), help = trans.arena.allowBerserkHelp().some, half = true ), - form3.hiddenFalse(form.prefix("berserkable")) + form3.hiddenFalse(form.prefix("berserkable")), + form3.checkbox( + form.prefix("streakable"), + trans.arena.arenaStreaks(), + help = trans.arena.arenaStreaksHelp().some, + half = true + ), + form3.hiddenFalse(form.prefix("streakable")) ), form3.split( + form3.checkbox( + form.prefix("rated"), + trans.site.rated(), + help = trans.site.ratedFormHelp().some + ), + form3.hiddenFalse(form.prefix("rated")), form3.checkbox( form.prefix("hasChat"), trans.site.chatRoom(), help = trans.arena.allowChatHelp().some, half = true ), - form3.hiddenFalse(form.prefix("hasChat")), - form3.checkbox( - form.prefix("streakable"), - trans.arena.arenaStreaks(), - help = trans.arena.arenaStreaksHelp().some, - half = true - ), - form3.hiddenFalse(form.prefix("streakable")) + form3.hiddenFalse(form.prefix("hasChat")) ) ) @@ -333,31 +339,24 @@ final class TourFields(tourForm: TournamentForm)(form: Form[?], tour: Option[Tou private def disabledAfterStart = tour.exists(!_.isCreated) def name = - form3.group(form.prefix("name"), trans.site.name()) { f => + form3.group( + form.prefix("name"), + trans.site.name(), + half = true, + help = frag( + trans.site.safeTournamentName(), + br, + trans.site.inappropriateNameWarning(), + br, + trans.site.emptyTournamentName() + ).some + ) { f => div( form3.input(f), - " ", if isTeamBattle then trans.team.teamBattle() else trans.arena.arena(), - br, - small(cls := "form-help")( - trans.site.safeTournamentName(), - br, - trans.site.inappropriateNameWarning(), - br, - trans.site.emptyTournamentName() - ) + br ) - } - - def rated = - frag( - form3.checkbox( - form.prefix("rated"), - trans.site.rated(), - help = trans.site.ratedFormHelp().some - ), - form3.hiddenFalse(form.prefix("rated")) - ) + }(cls := "tour-name") def variant = form3.group(form.prefix("variant"), trans.site.variant(), half = true)( form3.select( @@ -388,16 +387,22 @@ final class TourFields(tourForm: TournamentForm)(form: Form[?], tour: Option[Tou ) def minutes = form3.group(form.prefix("minutes"), trans.site.duration(), half = true): - form3.select(_, TournamentForm.minuteChoicesKeepingCustom(tour)) + if tour.forall(t => lila.tournament.TournamentForm.minutes contains t.minutes) + then form3.select(_, TournamentForm.minuteChoicesKeepingCustom(tour)) + else form3.input(_)(tpe := "number") def waitMinutes = form3.group(form.prefix("waitMinutes"), trans.site.timeBeforeTournamentStarts(), half = true): form3.select(_, TournamentForm.waitMinuteChoices) - def description(half: Boolean) = + def waitStart = + form3.fieldset("Start date", toggle = tour.forall(_.isCreated).some)( + form3.split(waitMinutes, startDate) + ) + def description = form3.group( form.prefix("description"), trans.site.tournDescription(), help = trans.site.tournDescriptionHelp().some, - half = half + half = true )(form3.textarea(_)(rows := 4)) def entryCode = form3.group( @@ -406,20 +411,16 @@ final class TourFields(tourForm: TournamentForm)(form: Form[?], tour: Option[Tou help = trans.site.makePrivateTournament().some, half = true )(form3.input(_)(autocomplete := "off")) - def startDate = - form3.group( - form.prefix("startDate"), - trans.arena.customStartDate(), - help = trans.arena.customStartDateHelp().some - )(form3.flatpickr(_)) + def startDate = tour + .forall(_.isCreated) + .option: + form3.group( + form.prefix("startDate"), + trans.arena.customStartDate(), + help = trans.arena.customStartDateHelp().some, + half = true + )(form3.flatpickr(_)) def advancedSettings = frag( - errMsg(form.prefix("conditions")), - p( - strong(dataIcon := Icon.CautionTriangle, cls := "text")(trans.site.recommendNotTouching()), - " ", - trans.site.fewerPlayers(), - " ", - a(cls := "show")(trans.site.showAdvancedSettings()) - ) + errMsg(form.prefix("conditions")) ) diff --git a/modules/ublog/src/main/UblogMarkup.scala b/modules/ublog/src/main/UblogMarkup.scala index 8131cdaa348f7..2aa51ac2724dc 100644 --- a/modules/ublog/src/main/UblogMarkup.scala +++ b/modules/ublog/src/main/UblogMarkup.scala @@ -39,7 +39,7 @@ final class UblogMarkup( private val cache = cacheApi[(UblogPostId, Markdown), Html](2048, "ublog.markup"): _.maximumSize(2048) - .expireAfterWrite(if mode.isProd then 15 minutes else 1 second) + .expireAfterWrite(if mode.isProd then 20 minutes else 1 second) .buildAsyncFuture: (id, markdown) => Bus .ask("lpv")(AllPgnsFromText(markdown.value, _)) diff --git a/modules/ublog/src/main/ui/UblogFormUi.scala b/modules/ublog/src/main/ui/UblogFormUi.scala index 7cab4c929c97e..bd99c2d14c69f 100644 --- a/modules/ublog/src/main/ui/UblogFormUi.scala +++ b/modules/ublog/src/main/ui/UblogFormUi.scala @@ -18,7 +18,7 @@ final class UblogFormUi(helpers: Helpers, ui: UblogUi)( def create(user: User, f: Form[UblogForm.UblogPostData], captcha: Captcha)(using Context) = FormPage(s"${trans.ublog.xBlog.txt(user.username)} • ${trans.ublog.newPost.txt()}") .js(captchaEsmInit): - main(cls := "page-menu page-small")( + main(cls := "page-menu page")( ui.menu(Left(user.id)), div(cls := "page-menu__content box ublog-post-form")( standardFlash, @@ -30,7 +30,7 @@ final class UblogFormUi(helpers: Helpers, ui: UblogUi)( def edit(post: UblogPost, f: Form[UblogForm.UblogPostData])(using ctx: Context) = FormPage(s"${trans.ublog.xBlog.txt(titleNameOrId(post.created.by))} • ${post.title}"): - main(cls := "page-menu page-small")( + main(cls := "page-menu page")( ui.menu(Left(post.created.by)), div(cls := "page-menu__content box ublog-post-form")( standardFlash, @@ -41,7 +41,6 @@ final class UblogFormUi(helpers: Helpers, ui: UblogUi)( ), a(href := ui.urlOfPost(post), dataIcon := Icon.Eye, cls := "text", targetBlank)("Preview") ), - image(post), inner(f, Right(post), none), postForm( cls := "ublog-post-form__delete", @@ -65,14 +64,7 @@ final class UblogFormUi(helpers: Helpers, ui: UblogUi)( action := post.fold(u => routes.Ublog.create(u.username), p => routes.Ublog.update(p.id)) )( form3.globalError(form), - post.toOption.map { p => - frag( - form3.split( - form3.group(form("imageAlt"), trans.ublog.imageAlt(), half = true)(form3.input(_)), - form3.group(form("imageCredit"), trans.ublog.imageCredit(), half = true)(form3.input(_)) - )(cls := s"ublog-post-form__image-text ${p.image.isDefined.so("visible")}") - ) - }, + post.toOption.map(image(_, form)), form3.group(form("title"), trans.ublog.postTitle())(form3.input(_)(autofocus)), form3.group(form("intro"), trans.ublog.postIntro())(form3.input(_)(autofocus)), form3.group( @@ -130,44 +122,51 @@ final class UblogFormUi(helpers: Helpers, ui: UblogUi)( ) ) - private def image(post: UblogPost)(using ctx: Context) = - div(cls := "ublog-image-edit", data("post-url") := routes.Ublog.image(post.id))( - ui.thumbnail(post, _.Size.Small)( - cls := "drop-target " + post.image.isDefined.so("user-image"), - attr("draggable") := "true" - ), - div( - if ctx.is(post.created.by) then - frag( - p(strong(trans.ublog.uploadAnImageForYourPost())), - p( - trans.ublog.safeToUseImages(), - fragList( - List( - "unsplash.com" -> "https://unsplash.com", - "commons.wikimedia.org" -> "https://commons.wikimedia.org", - "pixabay.com" -> "https://pixabay.com", - "pexels.com" -> "https://pexels.com", - "piqsels.com" -> "https://piqsels.com", - "freeimages.com" -> "https://freeimages.com" - ).map: (name, url) => - a(href := url, targetBlank)(name) + private def image(post: UblogPost, form: Form[UblogForm.UblogPostData])(using ctx: Context) = + form3.fieldset("Image", toggle = true.some)( + div(cls := "form-group ublog-image-edit", data("post-url") := routes.Ublog.image(post.id))( + ui.thumbnail(post, _.Size.Small)( + cls := "drop-target " + post.image.isDefined.so("user-image"), + attr("draggable") := "true" + ), + div( + if ctx.is(post.created.by) then + frag( + p(strong(trans.ublog.uploadAnImageForYourPost())), + p( + trans.ublog.safeToUseImages(), + fragList( + List( + "unsplash.com" -> "https://unsplash.com", + "commons.wikimedia.org" -> "https://commons.wikimedia.org", + "pixabay.com" -> "https://pixabay.com", + "pexels.com" -> "https://pexels.com", + "piqsels.com" -> "https://piqsels.com", + "freeimages.com" -> "https://freeimages.com" + ).map: (name, url) => + a(href := url, targetBlank)(name) + ) + ), + p(trans.ublog.useImagesYouMadeYourself()), + p(strong(trans.streamer.maxSize(s"${lila.memo.PicfitApi.uploadMaxMb}MB."))), + form3.file.selectImage() + ) + else + postForm( + action := routes.Ublog.image(post.id), + enctype := "multipart/form-data" + )( + post.image.isDefined.option(submitButton(cls := "button button-red confirm"): + trans.ublog.deleteImage() ) - ), - p(trans.ublog.useImagesYouMadeYourself()), - p(strong(trans.streamer.maxSize(s"${lila.memo.PicfitApi.uploadMaxMb}MB."))), - form3.file.selectImage() - ) - else - postForm( - cls := "ublog-post-form__image", - action := routes.Ublog.image(post.id), - enctype := "multipart/form-data" - )( - post.image.isDefined.option(submitButton(cls := "button button-red confirm"): - trans.ublog.deleteImage() ) - ) + ) + ), + post.image.isDefined.option( + form3.split( + form3.group(form("imageAlt"), trans.ublog.imageAlt(), half = true)(form3.input(_)), + form3.group(form("imageCredit"), trans.ublog.imageCredit(), half = true)(form3.input(_)) + )(cls := s"ublog-post-form__image-text visible") ) ) diff --git a/modules/ui/src/main/helper/Form3.scala b/modules/ui/src/main/helper/Form3.scala index e7a014b499010..91d52d25fb21d 100644 --- a/modules/ui/src/main/helper/Form3.scala +++ b/modules/ui/src/main/helper/Form3.scala @@ -178,8 +178,14 @@ final class Form3(formHelper: FormHelper & I18nHelper, flairApi: FlairApi): // allows disabling of a field that defaults to true def hiddenFalse(field: Field): Tag = hidden(field, "false".some) - def passwordModified(field: Field, content: Frag)(modifiers: Modifier*)(using Translate): Frag = - group(field, content)(input(_, typ = "password")(required)(modifiers)) + def passwordModified(field: Field, content: Frag, reveal: Boolean = true)( + modifiers: Modifier* + )(using Translate): Frag = + group(field, content): f => + div(cls := "password-wrapper")( + input(f, typ = "password")(required)(modifiers), + reveal.option(button(cls := "password-reveal", tpe := "button", dataIcon := Icon.Eye)) + ) def passwordComplexityMeter(labelContent: Frag): Frag = div(cls := "password-complexity")( @@ -192,8 +198,14 @@ final class Form3(formHelper: FormHelper & I18nHelper, flairApi: FlairApi): form.globalError.map: err => div(cls := "form-group is-invalid")(error(err)) - def fieldset(legend: Frag): Tag = - st.fieldset(cls := "form-fieldset")(st.legend(legend)) + def fieldset(legend: Frag, toggle: Option[Boolean] = none): Tag = + st.fieldset( + cls := List( + "form-fieldset" -> true, + "form-fieldset--toggle" -> toggle.isDefined, + "form-fieldset--toggle-off" -> toggle.has(false) + ) + )(st.legend(toggle.map(_ => tabindex := 0))(legend)) private val dataEnableTime = attr("data-enable-time") private val dataTime24h = attr("data-time_24h") @@ -246,4 +258,4 @@ final class Form3(formHelper: FormHelper & I18nHelper, flairApi: FlairApi): def image(name: String): Frag = st.input(tpe := "file", st.name := name, accept := "image/png, image/jpeg, image/webp") def pgn(name: String): Frag = st.input(tpe := "file", st.name := name, accept := ".pgn") - def selectImage = button(cls := "button select-image")("select image") + def selectImage = button(cls := "button select-image", tpe := "button")("Select image") diff --git a/modules/ui/src/main/scalatags.scala b/modules/ui/src/main/scalatags.scala index c787f6f03c46d..74dd0feed5603 100644 --- a/modules/ui/src/main/scalatags.scala +++ b/modules/ui/src/main/scalatags.scala @@ -7,6 +7,7 @@ import scalalib.Render import scalatags.Text.all.* import scalatags.Text.{ Aggregate, Cap, GenericAttr } import scalatags.text.Builder +import io.mola.galimatias.URL // collection of lila attrs trait ScalatagsAttrs: @@ -108,6 +109,7 @@ trait ScalatagsTemplate /* Convert play URLs to scalatags attributes with toString */ given GenericAttr[Call] = GenericAttr[Call] + given GenericAttr[URL] = GenericAttr[URL] object ScalatagsTemplate extends ScalatagsTemplate @@ -118,6 +120,7 @@ trait ScalatagsExtensions: export lila.core.perm.Granter given Render[Icon] = _.value + given Render[URL] = _.toString given [A](using Render[A]): Conversion[A, Frag] = a => StringFrag(a.render) diff --git a/modules/user/src/main/Flags.scala b/modules/user/src/main/Flags.scala index 150589bf31255..80cb40bc83c11 100644 --- a/modules/user/src/main/Flags.scala +++ b/modules/user/src/main/Flags.scala @@ -208,6 +208,8 @@ object Flags extends lila.core.user.FlagApi: C("PR", "Puerto Rico"), C("PS", "Palestine"), C("PT", "Portugal"), + C("PT-20", "Azores"), + C("PT-30", "Madeira"), C("PW", "Palau"), C("PY", "Paraguay"), C("QA", "Qatar"), diff --git a/modules/user/src/main/Profile.scala b/modules/user/src/main/Profile.scala index 92f1d155a7f08..db945490d1ba6 100644 --- a/modules/user/src/main/Profile.scala +++ b/modules/user/src/main/Profile.scala @@ -23,8 +23,7 @@ object Profile: def filterTroll(troll: Boolean): Profile = p.copy( bio = p.bio.ifFalse(troll), - firstName = p.firstName.ifFalse(troll), - lastName = p.lastName.ifFalse(troll), + realName = p.realName.ifFalse(troll), location = p.location.ifFalse(troll), links = p.links.ifFalse(troll) ) diff --git a/modules/user/src/main/UserForm.scala b/modules/user/src/main/UserForm.scala index a7986d85a9131..9ae6a0864a3fb 100644 --- a/modules/user/src/main/UserForm.scala +++ b/modules/user/src/main/UserForm.scala @@ -42,8 +42,7 @@ final class UserForm: "flag" -> optional(text.verifying(Flags.codeSet contains _)), "location" -> optional(cleanNoSymbolsAndNonEmptyText(maxLength = 80)), "bio" -> optional(cleanNoSymbolsAndNonEmptyText(maxLength = 400)), - "firstName" -> nameField, - "lastName" -> nameField, + "realName" -> optional(cleanNoSymbolsText(minLength = 1, maxLength = 100)), "fideRating" -> optional(number(min = 1400, max = 3000)), "uscfRating" -> optional(number(min = 100, max = 3000)), "ecfRating" -> optional(number(min = 0, max = 3000)), @@ -58,8 +57,6 @@ final class UserForm: def flair(using Me) = Form[Option[Flair]]: single(FlairApi.formPair()) - private def nameField = optional(cleanNoSymbolsText(minLength = 1, maxLength = 20)) - object UserForm: val note = Form: diff --git a/modules/user/src/main/UserRepo.scala b/modules/user/src/main/UserRepo.scala index fe4fde0760da4..bb7d297848866 100644 --- a/modules/user/src/main/UserRepo.scala +++ b/modules/user/src/main/UserRepo.scala @@ -154,6 +154,9 @@ final class UserRepo(c: Coll)(using Executor) extends lila.core.user.UserRepo(c) def setProfile(id: UserId, profile: Profile): Funit = coll.updateField($id(id), F.profile, profile).void + def setRealName(id: UserId, name: String): Funit = + coll.updateField($id(id), s"${F.profile}.realName", name).void + def setUsernameCased(id: UserId, name: UserName): Funit = if id.is(name) then coll.update diff --git a/modules/web/src/main/Limiters.scala b/modules/web/src/main/Limiters.scala index 3bbfb32c617fe..d928b4ba29cb4 100644 --- a/modules/web/src/main/Limiters.scala +++ b/modules/web/src/main/Limiters.scala @@ -143,5 +143,7 @@ final class Limiters(using Executor, lila.core.config.RateLimit): val studyPgn = RateLimit[IpAddress](credits = 31, duration = 1.minute, key = "export.study.pgn.ip") + val relayPgn = RateLimit[IpAddress](credits = 61, duration = 1.minute, key = "export.relay.pgn.ip") + val teamKick = RateLimit.composite[IpAddress](key = "team.kick.api.ip")(("fast", 10, 2.minutes), ("slow", 50, 1.day)) diff --git a/modules/web/src/main/ui/AuthUi.scala b/modules/web/src/main/ui/AuthUi.scala index 587d40472d729..8f2da7dddea76 100644 --- a/modules/web/src/main/ui/AuthUi.scala +++ b/modules/web/src/main/ui/AuthUi.scala @@ -197,8 +197,8 @@ email.setCustomValidity(email.validity.patternMismatch ? currentError : ""); Context )(using me: Me) = Page(s"${me.username} - ${trans.site.changePassword.txt()}") - .css("bits.form3") - .js(jsModuleInit("bits.passwordComplexity")): + .css("bits.auth") + .js(jsModuleInit("bits.login", "reset")): main(cls := "page-small box box-pad")( boxTop( (ok match diff --git a/modules/web/src/main/ui/LearnUi.scala b/modules/web/src/main/ui/LearnUi.scala index 72054f2ef7631..f05e1b496518a 100644 --- a/modules/web/src/main/ui/LearnUi.scala +++ b/modules/web/src/main/ui/LearnUi.scala @@ -10,12 +10,18 @@ final class LearnUi(helpers: Helpers): import helpers.{ *, given } import trans.{ learn as trl } - def apply(data: Option[play.api.libs.json.JsValue])(using Context) = + def apply(data: Option[play.api.libs.json.JsValue])(using ctx: Context) = Page(s"${trl.learnChess.txt()} - ${trl.byPlaying.txt()}") .js: PageModule( "learn", - Json.obj("data" -> data, "i18n" -> i18nJsObject(i18nKeys)) + Json.obj( + "data" -> data, + "i18n" -> i18nJsObject(i18nKeys), + "pref" -> Json.obj( + "coords" -> ctx.pref.coords + ) + ) ) .css("learn") .graph( diff --git a/modules/web/src/main/ui/contact.scala b/modules/web/src/main/ui/contact.scala index f94f159b55e55..50e12a301cec5 100644 --- a/modules/web/src/main/ui/contact.scala +++ b/modules/web/src/main/ui/contact.scala @@ -222,18 +222,6 @@ object contact: ) ) ), - Leaf( - "broadcast", - wantBroadcastTournament(), - frag( - p(a(href := routes.RelayTour.help)(learnHowToMakeBroadcasts()), "."), - p( - contactAboutOfficialBroadcasts(), - " ", - sendEmailAt(contactEmailLink("broadcast@lichess.org")) - ) - ) - ), frag( p(doNotMessageModerators()), p(sendAppealTo(a(href := routes.Appeal.home)(routes.Appeal.home.url))), @@ -322,7 +310,7 @@ object contact: "dmca", "DMCA / Intellectual Property Take Down Notice", p( - a(href := dmcaUrl)("Complete this form"), + a(href := "/dmca")("Complete this form"), " ", "if you are the original copyright holder, or an agent acting on behalf of the copyright holder, and believe Lichess is hosting work(s) you hold the copyright to." ) @@ -339,5 +327,3 @@ object contact: ) ) ) - - val dmcaUrl = "/dmca" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47e38a009a786..1bba8567486ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -496,6 +496,9 @@ importers: chessops: specifier: ^0.14.1 version: 0.14.1 + common: + specifier: workspace:* + version: link:../common snabbdom: specifier: 3.5.1 version: 3.5.1 diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 88f7a4009c0d2..5cfd3dfbe088e 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -85,7 +85,7 @@ object Dependencies { object play { val playVersion = "2.8.18-lila_3.18" - val json = "org.playframework" %% "play-json" % "3.0.3" + val json = "org.playframework" %% "play-json" % "3.0.4" val api = "com.typesafe.play" %% "play" % playVersion val server = "com.typesafe.play" %% "play-server" % playVersion val netty = "com.typesafe.play" %% "play-netty-server" % playVersion @@ -94,7 +94,7 @@ object Dependencies { } object playWs { - val version = "2.2.7" + val version = "2.2.8" val ahc = "com.typesafe.play" %% "play-ahc-ws-standalone" % version val json = "com.typesafe.play" %% "play-ws-standalone-json" % version val bundle = Seq(ahc, json) diff --git a/public/images/flags/PT-20.png b/public/images/flags/PT-20.png new file mode 100644 index 0000000000000..4cef77e54bee7 Binary files /dev/null and b/public/images/flags/PT-20.png differ diff --git a/public/images/flags/PT-30.png b/public/images/flags/PT-30.png new file mode 100644 index 0000000000000..8f170fd3f3212 Binary files /dev/null and b/public/images/flags/PT-30.png differ diff --git a/public/piece-css/cardinal.css b/public/piece-css/cardinal.css index a4d92e14347c7..3d0be4ae71e21 100644 --- a/public/piece-css/cardinal.css +++ b/public/piece-css/cardinal.css @@ -1,12 +1,12 @@ -.is2d .pawn.white {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MG1tIiBoZWlnaHQ9IjUwbW0iIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNDEyNy4zIiB4Mj0iNDIzNS43IiB5MT0iLTI1NTguNCIgeTI9Ii0yNTU4LjQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjI3Njc3IDAgMCAuMjc1NTUgLTExMzIuMyA3MzEuOTYpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZmZmIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZTZlNmU2Ii8+PC9saW5lYXJHcmFkaWVudD48ZmlsdGVyIGlkPSJiIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPjxmZUZsb29kIGZsb29kLWNvbG9yPSIjMDAwIiBmbG9vZC1vcGFjaXR5PSIuNDk4IiByZXN1bHQ9ImZsb29kIi8+PGZlQ29tcG9zaXRlIGluPSJmbG9vZCIgaW4yPSJTb3VyY2VHcmFwaGljIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iY29tcG9zaXRlMSIvPjxmZUdhdXNzaWFuQmx1ciBpbj0iY29tcG9zaXRlMSIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249Ii4zIi8+PGZlT2Zmc2V0IGR4PSIxIiBkeT0iMSIgcmVzdWx0PSJvZmZzZXQiLz48ZmVDb21wb3NpdGUgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0ib2Zmc2V0IiByZXN1bHQ9ImNvbXBvc2l0ZTIiLz48L2ZpbHRlcj48L2RlZnM+PHBhdGggZmlsbD0idXJsKCNhKSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuMTM1IiBkPSJNMjUuMDI0IDQzLjQwMUgxMS4xMTljLTIuNTUxLTUuODg1IDQuMjEzLTExLjM0MSA4Ljk2OC0xMy4xOTQtNS42ODItMy4xNi0yLjYwMi0xMS4yMTkgMi4yNy0xMS44NzMtMS4xNi0uNzYzLTEuNzQtMi4zOTMtMS43NC0zLjcgMC0xLjA5LjQ2My0yLjA3MiAxLjI3NS0yLjgzNC44MTItLjc2MyAxLjg1Ni0xLjIgMy4xMzEtMS4yIDEuMTYgMCAyLjIwNC40MzcgMy4xMzIgMS4yLjgxMi43NjIgMS4yNzUgMS43NDMgMS4yNzUgMi44MzMgMCAxLjMwOC0uNTggMi45MzgtMS43NCAzLjcwMSA1LjMzNiAyLjA3IDcuMjU3IDkuNjkzIDIuMjcgMTEuODczIDYuNDk0IDIuMjg5IDExLjA1NiA4LjA3MiA4Ljk2OCAxMy4xOTR6IiBjbGFzcz0ic3QzMSIgZmlsdGVyPSJ1cmwoI2IpIiB0cmFuc2Zvcm09Im1hdHJpeCguOTY2NTggMCAwIC45NzI0NSAuODMzIDEuMjQzKSIvPjwvc3ZnPg==')} -.is2d .knight.white {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MG1tIiBoZWlnaHQ9IjUwbW0iIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iLTQ1NS4zOSIgeDI9Ii00MTkuNDEiIHkxPSItMzM4LjIzIiB5Mj0iLTMzOC4yMyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZjZmNmOCIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2U3ZTdlMyIvPjwvbGluZWFyR3JhZGllbnQ+PGZpbHRlciBpZD0iYiIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVGbG9vZCBmbG9vZC1jb2xvcj0iIzAwMCIgZmxvb2Qtb3BhY2l0eT0iLjQ5OCIgcmVzdWx0PSJmbG9vZCIvPjxmZUNvbXBvc2l0ZSBpbj0iZmxvb2QiIGluMj0iU291cmNlR3JhcGhpYyIgb3BlcmF0b3I9ImluIiByZXN1bHQ9ImNvbXBvc2l0ZTEiLz48ZmVHYXVzc2lhbkJsdXIgaW49ImNvbXBvc2l0ZTEiIHJlc3VsdD0iYmx1ciIgc3RkRGV2aWF0aW9uPSIuNiIvPjxmZU9mZnNldCBkeD0iMS42IiBkeT0iMS40IiByZXN1bHQ9Im9mZnNldCIvPjxmZUNvbXBvc2l0ZSBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJvZmZzZXQiIHJlc3VsdD0iY29tcG9zaXRlMiIvPjwvZmlsdGVyPjwvZGVmcz48cGF0aCBmaWxsPSJ1cmwoI2EpIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS4wOTkiIGQ9Ik0tNDQzLjkyLTMzMi45NWMyLjEtMS4xOTggMy4zMjItMS4xODMgNS40NjMtMi4xOTkuMjI0IDcuNDI1LTkuOTAxIDcuNDU2LTguMDg3IDE1LjM2bDI2LjQwNS4wMDJzMy4wOTYtMzIuMjctMTYuNzgyLTMzLjYyMmMwIDAtMS45MTQtMy42MDUtMy45MjUtMy4yNSAwIDAtMS4wNjUuODM4LS40NTcgMy4yMDdsLTIuMzA0Ljc0NnMtMy4yMTQtMi4wNzItNC4xMjQtMS4yN2MtLjg1OC4zNyAxLjA5OCAzLjI4IDEuODc2IDMuOTg2LS43ODggMS4xNDItOC41NDIgMTIuMTA4LTguOTYgMTUuNjgtLjI2NiAyLjI3NyAyLjAyMSAzLjUxOCAzLjcxNiA0LjExOS45NjQuMzQxIDEuNzM2LjQ3NiAxLjczNi40NzYgMS40MjUtLjI1NiAzLjM0My0yLjAzNyA1LjQ0My0zLjIzNXoiIGZpbHRlcj0idXJsKCNiKSIgdHJhbnNmb3JtPSJtYXRyaXgoMS4wMDA4IDAgMCAxLjAwMDEgNDYyLjc1IDM2My4yNikiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjEiIGQ9Ik0yMy45NDEgMjguMDg2czQuNDMzLTEuODY3IDQuMjI0LTUuODM1Ii8+PHBhdGggc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMS40IiBkPSJNMTkuMTA0IDE4LjQ3M3MuNTk0LTEuODQ2IDMuNDUzLTIuMjk0Ii8+PGVsbGlwc2UgY3g9IjIxLjAyNyIgY3k9IjE4LjAwMSIgcng9IjEuMjQyIiByeT0iMS4xNjgiIHN0eWxlPSJwYWludC1vcmRlcjptYXJrZXJzIGZpbGwgc3Ryb2tlIi8+PHBhdGggZmlsbD0iI2ZmZiIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMS40IiBkPSJNOS4xNyAyOS4yNDFzLjI1NC0uNjgyLjkyNC0xLjExOCIvPjxwYXRoIGZpbGw9IiNmZmYiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuMiIgZD0iTTExLjY0IDMyLjI4M2MuNjktLjg4NyAxLjU4My0xLjMxOSAyLjM4NC0xLjk1NyIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjQiIGQ9Ik0zMC44MDYgMTQuODcyYzQuMzA1IDIuNjMzIDguNDYgOS4yNSA4LjExIDI2LjA4Ii8+PC9zdmc+')} -.is2d .bishop.white {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MG1tIiBoZWlnaHQ9IjUwbW0iIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGZpbHRlciBpZD0iYyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249IjAuMDEgMC4wMSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+PGZlRmxvb2QgZmxvb2QtY29sb3I9IiMwMDAiIGZsb29kLW9wYWNpdHk9Ii40OTgiIHJlc3VsdD0iZmxvb2QiLz48ZmVDb21wb3NpdGUgaW49ImZsb29kIiBpbjI9IlNvdXJjZUdyYXBoaWMiIG9wZXJhdG9yPSJpbiIgcmVzdWx0PSJjb21wb3NpdGUxIi8+PGZlR2F1c3NpYW5CbHVyIGluPSJjb21wb3NpdGUxIiByZXN1bHQ9ImJsdXIiIHN0ZERldmlhdGlvbj0iLjMiLz48ZmVPZmZzZXQgZHg9IjEiIGR5PSIxIiByZXN1bHQ9Im9mZnNldCIvPjxmZUNvbXBvc2l0ZSBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJvZmZzZXQiIHJlc3VsdD0iY29tcG9zaXRlMiIvPjwvZmlsdGVyPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeDE9IjEzMTk3IiB4Mj0iMTMzNDEiIHkxPSItOTU5MSIgeTI9Ii05NTkxIiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC0zNDg1LjcgMjU2Mi42KSBzY2FsZSguMjY0NTgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZmZmIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZTZlNmU2Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZmlsbD0idXJsKCNhKSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuMSIgZD0iTTI1IDYuNTVjLS44NzggMC0xLjY1NC4yOS0yLjI2MS45MDMtLjY0MS42MTItLjk0NiAxLjMyMS0uOTQ2IDIuMTU5IDAgMS4yMjUuNTc0IDIuMTI3IDEuNzU1IDIuNzQtMi45NjkgMy4yODUtOC43MDcgNS44MjEtOC44MSAxMC44MjcuMDA3IDIuNjc1IDEuNDY2IDQuNzY0IDMuMzA4IDYuOGwtMS4xMTQgNS44MzNjMS42OTcuNTQyIDMuMDkuOTQyIDQuODI3IDEuMTI4LTMuODgyIDQuNTc2LTEwLjc4Ny0xLjc0LTE1LjIwOSAyLjkzM2wyLjMzIDMuNTc3YzUuNTkzLTMuOTYyIDEzLjM3NSAzLjY3MyAxNi4xMi0zLjk2MiAyLjc0NiA3LjYzNSAxMC41MjguMDAzIDE2LjEyIDMuOTYybDIuMzMtMy41NzdjLTQuNDIyLTQuNjczLTExLjMyNyAxLjY0My0xNS4yMDktMi45MzMgMS43MzgtLjE4NiAzLjEzLS41ODYgNC44MjgtMS4xMjhsLTEuMTE1LTUuODMzYzEuODQzLTIuMDM2IDMuMzAyLTQuMTI1IDMuMzA5LTYuOC0uMTAzLTUuMDA2LTUuODQyLTcuNTQyLTguODExLTEwLjgyOCAxLjE4MS0uNjEyIDEuNzU1LTEuNTE0IDEuNzU1LTIuNzQgMC0uODM3LS4zMDQtMS41NDYtLjk0NS0yLjE1OC0uNjA4LS42MTMtMS4zODQtLjkwMy0yLjI2MS0uOTAzeiIgZmlsdGVyPSJ1cmwoI2IpIi8+PGVsbGlwc2UgY3g9IjI3MjAuMyIgY3k9Ii0yNzEuNCIgY2xhc3M9InN0MTUiIGZpbHRlcj0idXJsKCNjKSIgcng9IjE2LjMiIHJ5PSIyLjUiIHRyYW5zZm9ybT0ibWF0cml4KC4zMzIzMiAwIDAgLjI0OTk4IC04NzkuMDEgMTAyLjQ3KSIvPjxlbGxpcHNlIGN4PSIyNSIgY3k9IjkuNjExIiBjbGFzcz0ic3QxNSIgcng9IjEuMTQiIHJ5PSIxLjE0NyIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIxLjQiIGQ9Ik0yMS4zMzMgMjMuMjY2aDcuMzMzTTI1IDE5LjkzMnY2Ljc1MiIvPjwvc3ZnPg==')} -.is2d .rook.white {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MG1tIiBoZWlnaHQ9IjUwbW0iIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNDUwMS41IiB4Mj0iNDU5NC42IiB5MT0iLTU3Mi40IiB5Mj0iLTU3Mi40IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4zNDIwOCAwIDAgLjI4MzcgLTE1MzAuOCAxODcuMzkpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZmZmIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZTZlNmU2Ii8+PC9saW5lYXJHcmFkaWVudD48ZmlsdGVyIGlkPSJiIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPjxmZUZsb29kIGZsb29kLWNvbG9yPSIjMDAwIiBmbG9vZC1vcGFjaXR5PSIuNDk4IiByZXN1bHQ9ImZsb29kIi8+PGZlQ29tcG9zaXRlIGluPSJmbG9vZCIgaW4yPSJTb3VyY2VHcmFwaGljIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iY29tcG9zaXRlMSIvPjxmZUdhdXNzaWFuQmx1ciBpbj0iY29tcG9zaXRlMSIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249Ii4zIi8+PGZlT2Zmc2V0IGR4PSIxIiBkeT0iMSIgcmVzdWx0PSJvZmZzZXQiLz48ZmVDb21wb3NpdGUgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0ib2Zmc2V0IiByZXN1bHQ9ImNvbXBvc2l0ZTIiLz48L2ZpbHRlcj48L2RlZnM+PHBhdGggZmlsbD0idXJsKCNhKSIgc3Ryb2tlPSIjMDEwMTAxIiBzdHJva2Utd2lkdGg9IjEuMTQ0IiBkPSJNMjEuOTMyIDYuNTQ2VjkuNDhoLTQuMDkxVjYuODkyaC01Ljc5NnY3Ljk3NWw0LjUzMyAzLjE0MnYxMi41NjdsLTMuODUgMi40ODZ2NS4yMTNIOS42NTh2NS4xNzloMzAuNjgydi01LjE4aC0zLjA2OHYtNS4yMTJsLTMuODUtMi40ODZWMTguMDQzbDQuNTMyLTMuMjExdi03Ljk0aC01Ljc5NnYyLjU4N2gtNC40MzJWNi41NDZIMjQuODN6IiBjbGFzcz0ic3QxNCIgZmlsdGVyPSJ1cmwoI2IpIiB0cmFuc2Zvcm09Im1hdHJpeCgxLjAwNTUgMCAwIC45MTk4IC0uMTM3IDMuNTA1KSIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIxLjQiIGQ9Ik0xOC44MjkgMzEuNDM4aDExLjk5OE0xOC44MjkgMjAuMDA2aDExLjk5OCIvPjwvc3ZnPg==')} -.is2d .queen.white {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MG1tIiBoZWlnaHQ9IjUwbW0iIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGZpbHRlciBpZD0iYyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249IjAuMDEgMC4wMSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+PGZlRmxvb2QgZmxvb2QtY29sb3I9IiMwMDAiIGZsb29kLW9wYWNpdHk9Ii40OTgiIHJlc3VsdD0iZmxvb2QiLz48ZmVDb21wb3NpdGUgaW49ImZsb29kIiBpbjI9IlNvdXJjZUdyYXBoaWMiIG9wZXJhdG9yPSJpbiIgcmVzdWx0PSJjb21wb3NpdGUxIi8+PGZlR2F1c3NpYW5CbHVyIGluPSJjb21wb3NpdGUxIiByZXN1bHQ9ImJsdXIiIHN0ZERldmlhdGlvbj0iLjMiLz48ZmVPZmZzZXQgZHg9IjEiIGR5PSIxIiByZXN1bHQ9Im9mZnNldCIvPjxmZUNvbXBvc2l0ZSBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJvZmZzZXQiIHJlc3VsdD0iY29tcG9zaXRlMiIvPjwvZmlsdGVyPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeDE9Ii03MS42MzgiIHgyPSItMzAuNjc5IiB5MT0iLTgzLjMyNCIgeTI9Ii04My4zMjQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjk3NjQzIDAgMCAuOTkyODcgNzQuOTUyIDEwNy43MykiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZmYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNlNmU2ZTYiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cGF0aCBmaWxsPSJ1cmwoI2EpIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjEiIGQ9Ik0yNC45OTQgNi41NDl2MGMtMS41NjguMDA2LTIuODM1IDEuMTgxLTIuODM2IDIuNjMuMDAyIDEuMTkzLjg3MyAyLjIzNSAyLjEyMiAyLjUzOS0uNjg4IDQuNDUtMS45NjcgOS43MjYtMi42MzQgMTQuMTEybC00LjA3LTEyLjkyN2MuOTY4LS40NDQgMS41OC0xLjM1NiAxLjU4LTIuMzU0IDAtMS40NTItMS4yNzUtMi42My0yLjg0Ny0yLjYzLTEuNTcyLjAwMS0yLjg0NyAxLjE3OC0yLjg0NyAyLjYzLjAwMiAxLjIwNi44OSAyLjI1NiAyLjE1NyAyLjU0OGwtLjQ0IDEzLjI1OC01LjQ4Mi0xMC42MTFjLjk1MS0uNDUgMS41NS0xLjM1NCAxLjU1LTIuMzQgMC0xLjQ1My0xLjI3NS0yLjYzLTIuODQ3LTIuNjMtMS41NzMgMC0yLjg0NyAxLjE3Ny0yLjg0NyAyLjYzIDAgMS4zMzQgMS4wODQgMi40NTYgMi41MTkgMi42MWwyLjc2IDE2LjUwNyA0LjA1IDUuMjU4LTEuMDA1IDMuNjM0Yy0uMDQyLjY1NiA0Ljg0OSAyLjAyNyAxMS4xMjIgMi4wNCA2LjI3NC0uMDEzIDExLjE2NC0xLjM4NCAxMS4xMjItMi4wNGwtMS4wMDQtMy42MzQgNC4wNS01LjI1OCAyLjc2LTE2LjUwN2MxLjQzNS0uMTU0IDIuNTE4LTEuMjc2IDIuNTItMi42MSAwLTEuNDUzLTEuMjc1LTIuNjMtMi44NDgtMi42My0xLjU3MiAwLTIuODQ2IDEuMTc3LTIuODQ2IDIuNjMgMCAuOTg2LjU5OCAxLjg5IDEuNTUgMi4zNGwtNS40ODQgMTAuNjEtLjQzOS0xMy4yNTdjMS4yNjYtLjI5MiAyLjE1NS0xLjM0MiAyLjE1Ny0yLjU0OCAwLTEuNDUyLTEuMjc1LTIuNjMtMi44NDctMi42My0xLjU3Mi4wMDEtMi44NDcgMS4xNzgtMi44NDcgMi42MyAwIC45OTguNjEyIDEuOTEgMS41OCAyLjM1NGwtNC4wNyAxMi45MjdjLS42NjgtNC4zODYtMS45NDYtOS42NjMtMi42MzUtMTQuMTEyIDEuMjUtLjMwNCAyLjEyMS0xLjM0NiAyLjEyMy0yLjU0IDAtMS40NDgtMS4yNjgtMi42MjMtMi44MzYtMi42Mjl2MGgtLjAxMXoiIGZpbHRlcj0idXJsKCNiKSIgc3R5bGU9InBhaW50LW9yZGVyOm5vcm1hbCIvPjxlbGxpcHNlIGN4PSI0NzA4LjciIGN5PSItMjUxNy42IiBjbGFzcz0ic3QxNSIgZmlsdGVyPSJ1cmwoI2MpIiByeD0iMzIuMTI2IiByeT0iMi44NDQiIHRyYW5zZm9ybT0ibWF0cml4KC4yNTkzOSAwIDAgLjI5Mjk4IC0xMTk2LjQgNzc4LjEyKSIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNMTUuMTcyIDM0LjA3NnMyLjctMS4yNDkgOS44MDItMS4yNTZjNy4xMDMtLjAxIDkuOCAxLjI1NiA5LjggMS4yNTYiIHN0eWxlPSJwYWludC1vcmRlcjpzdHJva2UgZmlsbCBtYXJrZXJzIi8+PC9zdmc+')} -.is2d .king.white {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MG1tIiBoZWlnaHQ9IjUwbW0iIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGZpbHRlciBpZD0iYyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249IjAuMDEgMC4wMSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+PGZlRmxvb2QgZmxvb2QtY29sb3I9IiMwMDAiIGZsb29kLW9wYWNpdHk9Ii40OTgiIHJlc3VsdD0iZmxvb2QiLz48ZmVDb21wb3NpdGUgaW49ImZsb29kIiBpbjI9IlNvdXJjZUdyYXBoaWMiIG9wZXJhdG9yPSJpbiIgcmVzdWx0PSJjb21wb3NpdGUxIi8+PGZlR2F1c3NpYW5CbHVyIGluPSJjb21wb3NpdGUxIiByZXN1bHQ9ImJsdXIiIHN0ZERldmlhdGlvbj0iLjYiLz48ZmVPZmZzZXQgZHg9IjEuNiIgZHk9IjEuNCIgcmVzdWx0PSJvZmZzZXQiLz48ZmVDb21wb3NpdGUgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0ib2Zmc2V0IiByZXN1bHQ9ImNvbXBvc2l0ZTIiLz48L2ZpbHRlcj48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSIyOTg2LjQiIHgyPSIzMTI4LjQiIHkxPSIxNjIzLjgiIHkyPSIxNjIzLjgiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjI3MTQxIDAgMCAuMjcyMTggLTgwNC44MSAtNDE3LjQ1KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZmZiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2U2ZTZlNiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGZpbGw9InVybCgjYSkiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIxLjEiIGQ9Ik0yMy4yODMgNS41NXYzLjIzOGgtMy4zNjR2Mi45MmgzLjM2NHYxLjc1OGMtMy4zNjggMi4xMjctMi45OTYgNS43NC0yLjk5NiA1Ljc0QzkuMjc4IDEwLjY5LS4zODUgMjYuNzcgMTIuMzQzIDMyLjI2djguNzM0YzAgLjk1IDUuNjY3IDIuNDU2IDEyLjY1NyAyLjQ1NnMxMi42NTctMS41MDYgMTIuNjU3LTIuNDU2VjMyLjI2YzEyLjcyOC01LjQ5IDMuMDY2LTIxLjU2OS03Ljk0My0xMy4wNTMgMCAwIC4zNzItMy42MTMtMi45OTYtNS43NHYtMS43NThoMy4zNjR2LTIuOTJoLTMuMzY0VjUuNTUxaC0xLjcxN3oiIGZpbHRlcj0idXJsKCNiKSIvPjxlbGxpcHNlIGN4PSI3MS4wNzciIGN5PSIxMzEuNTQiIGNsYXNzPSJzdDE1IiBmaWx0ZXI9InVybCgjYykiIHJ4PSIzMi4xMjYiIHJ5PSIyLjg0NCIgdHJhbnNmb3JtPSJtYXRyaXgoLjI4NTMzIDAgMCAuMzIyMyA0LjcyIC0xLjk4KSIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIxLjQiIGQ9Ik0yNy4wMzIgMzAuMjY3YzEuNDktMTIuMTAyIDExLjk0My0xMi40NDEgMTMuMzY0LTcuMzggMS40MiA1LjA2Mi00LjczNiA3LjM4LTQuNzM2IDcuMzhzLTQuODc1LS42MzgtMTAuNjYtLjYzOC0xMC42Ni42MzgtMTAuNjYuNjM4LTYuMTU2LTIuMzE4LTQuNzM1LTcuMzggMTEuODc0LTQuNzIyIDEzLjM2NCA3LjM4Ii8+PC9zdmc+')} -.is2d .pawn.black {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MG1tIiBoZWlnaHQ9IjUwbW0iIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNDEyNy4yIiB4Mj0iNDIzNS43IiB5MT0iLTI1NTguMyIgeTI9Ii0yNTU4LjMiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjI2NzQ5IDAgMCAuMjY3OTkgLTEwOTMuNSA3MTMuMTEpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM2MzYzNjIi8+PHN0b3Agb2Zmc2V0PSIxIi8+PC9saW5lYXJHcmFkaWVudD48ZmlsdGVyIGlkPSJiIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPjxmZUZsb29kIGZsb29kLWNvbG9yPSIjMDAwIiBmbG9vZC1vcGFjaXR5PSIuNDk4IiByZXN1bHQ9ImZsb29kIi8+PGZlQ29tcG9zaXRlIGluPSJmbG9vZCIgaW4yPSJTb3VyY2VHcmFwaGljIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iY29tcG9zaXRlMSIvPjxmZUdhdXNzaWFuQmx1ciBpbj0iY29tcG9zaXRlMSIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249Ii4zIi8+PGZlT2Zmc2V0IGR4PSIxIiBkeT0iMSIgcmVzdWx0PSJvZmZzZXQiLz48ZmVDb21wb3NpdGUgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0ib2Zmc2V0IiByZXN1bHQ9ImNvbXBvc2l0ZTIiLz48L2ZpbHRlcj48L2RlZnM+PHBhdGggZmlsbD0idXJsKCNhKSIgc3Ryb2tlPSIjZTZlNmU2IiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuMSIgZD0iTTI1LjAxOSA0My40NUgxMS41OGMtMi40NjYtNS43MjQgNC4wNzItMTEuMDMgOC42NjgtMTIuODMyLTUuNDkzLTMuMDc0LTIuNTE1LTEwLjkxMSAyLjE5Mi0xMS41NDctMS4xMi0uNzQyLTEuNjgxLTIuMzI3LTEuNjgxLTMuNiAwLTEuMDYuNDQ4LTIuMDEzIDEuMjMzLTIuNzU1Ljc4NS0uNzQyIDEuNzkzLTEuMTY2IDMuMDI2LTEuMTY2IDEuMTIxIDAgMi4xMy40MjQgMy4wMjYgMS4xNjYuNzg1Ljc0MiAxLjIzMyAxLjY5NiAxLjIzMyAyLjc1NiAwIDEuMjcyLS41NiAyLjg1Ny0xLjY4MSAzLjU5OSA1LjE1NiAyLjAxNCA3LjAxMiA5LjQyNyAyLjE5MyAxMS41NDcgNi4yNzYgMi4yMjYgMTAuNjg1IDcuODUgOC42NjcgMTIuODMyeiIgY2xhc3M9InN0MzEiIGZpbHRlcj0idXJsKCNiKSIvPjwvc3ZnPg==')} -.is2d .knight.black {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MG1tIiBoZWlnaHQ9IjUwbW0iIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iLTQ1NS4zOSIgeDI9Ii00MTkuNDEiIHkxPSItMzM4LjIzIiB5Mj0iLTMzOC4yMyIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLjAwMDggMCAwIDEuMDAwMSA0NjIuNzUgMzYzLjI2KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzNjM2MzYyIvPjxzdG9wIG9mZnNldD0iMSIvPjwvbGluZWFyR3JhZGllbnQ+PGZpbHRlciBpZD0iYiIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVGbG9vZCBmbG9vZC1jb2xvcj0iIzAwMCIgZmxvb2Qtb3BhY2l0eT0iLjQ5OCIgcmVzdWx0PSJmbG9vZCIvPjxmZUNvbXBvc2l0ZSBpbj0iZmxvb2QiIGluMj0iU291cmNlR3JhcGhpYyIgb3BlcmF0b3I9ImluIiByZXN1bHQ9ImNvbXBvc2l0ZTEiLz48ZmVHYXVzc2lhbkJsdXIgaW49ImNvbXBvc2l0ZTEiIHJlc3VsdD0iYmx1ciIgc3RkRGV2aWF0aW9uPSIuNiIvPjxmZU9mZnNldCBkeD0iMS42IiBkeT0iMS40IiByZXN1bHQ9Im9mZnNldCIvPjxmZUNvbXBvc2l0ZSBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJvZmZzZXQiIHJlc3VsdD0iY29tcG9zaXRlMiIvPjwvZmlsdGVyPjwvZGVmcz48cGF0aCBmaWxsPSJ1cmwoI2EpIiBzdHJva2U9IiNlNmU2ZTYiIHN0cm9rZS13aWR0aD0iMS4xIiBkPSJNMTguNDc0IDMwLjI4NWMyLjEwMy0xLjE5OCAzLjMyNS0xLjE4MyA1LjQ2Ny0yLjIuMjI0IDcuNDI2LTkuOTA5IDcuNDU3LTguMDkzIDE1LjM2MmwyNi40MjYuMDAzUzQ1LjM3MiAxMS4xNzYgMjUuNDggOS44MjRjMCAwLTEuOTE1LTMuNjA1LTMuOTI4LTMuMjUgMCAwLTEuMDY2LjgzOC0uNDU4IDMuMjA3bC0yLjMwNi43NDZzLTMuMjE2LTIuMDcyLTQuMTI3LTEuMjdjLS44NTguMzY5IDEuMSAzLjI4IDEuODc4IDMuOTg2LS43OSAxLjE0Mi04LjU0OSAxMi4xMDktOC45NjcgMTUuNjgyLS4yNjcgMi4yNzcgMi4wMjMgMy41MTggMy43MTkgNC4xMmExMS45MSAxMS45MSAwIDAgMCAxLjczNy40NzZjMS40MjYtLjI1NiAzLjM0NS0yLjAzOCA1LjQ0OC0zLjIzNnoiIGZpbHRlcj0idXJsKCNiKSIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2U2ZTZlNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuMSIgZD0iTTIzLjk0MSAyOC4wODZzNC40MzMtMS44NjcgNC4yMjQtNS44MzUiLz48cGF0aCBzdHJva2U9IiNlNmU2ZTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjQiIGQ9Ik0xOS4xMDQgMTguNDczcy41OTQtMS44NDYgMy40NTMtMi4yOTQiLz48ZWxsaXBzZSBjeD0iMjEuMDI3IiBjeT0iMTguMDAxIiBmaWxsPSIjZTZlNmU2IiByeD0iMS4yNDIiIHJ5PSIxLjE2OCIgc3R5bGU9InBhaW50LW9yZGVyOm1hcmtlcnMgZmlsbCBzdHJva2UiLz48cGF0aCBmaWxsPSIjZmZmIiBzdHJva2U9IiNlNmU2ZTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjQiIGQ9Ik05LjE3IDI5LjI0MXMuMjU0LS42ODIuOTI0LTEuMTE4Ii8+PHBhdGggZmlsbD0iI2ZmZiIgc3Ryb2tlPSIjZTZlNmU2IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMS4yIiBkPSJNMTEuNjQgMzIuMjgzYy42OS0uODg3IDEuNTgzLTEuMzE5IDIuMzg0LTEuOTU3Ii8+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTZlNmU2IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuNCIgZD0iTTMwLjgwNiAxNC44NzJjNC4zMDUgMi42MzMgOC40NiA5LjI1IDguMTEgMjYuMDgiLz48L3N2Zz4=')} -.is2d .bishop.black {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MG1tIiBoZWlnaHQ9IjUwbW0iIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGZpbHRlciBpZD0iYyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249IjAuMDEgMC4wMSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+PGZlRmxvb2QgZmxvb2QtY29sb3I9IiMwMDAiIGZsb29kLW9wYWNpdHk9Ii40OTgiIHJlc3VsdD0iZmxvb2QiLz48ZmVDb21wb3NpdGUgaW49ImZsb29kIiBpbjI9IlNvdXJjZUdyYXBoaWMiIG9wZXJhdG9yPSJpbiIgcmVzdWx0PSJjb21wb3NpdGUxIi8+PGZlR2F1c3NpYW5CbHVyIGluPSJjb21wb3NpdGUxIiByZXN1bHQ9ImJsdXIiIHN0ZERldmlhdGlvbj0iLjMiLz48ZmVPZmZzZXQgZHg9IjEiIGR5PSIxIiByZXN1bHQ9Im9mZnNldCIvPjxmZUNvbXBvc2l0ZSBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJvZmZzZXQiIHJlc3VsdD0iY29tcG9zaXRlMiIvPjwvZmlsdGVyPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeDE9IjEzMTk3IiB4Mj0iMTMzNDEiIHkxPSItOTU5MS4xIiB5Mj0iLTk1OTEuMSIgZ3JhZGllbnRUcmFuc2Zvcm09InRyYW5zbGF0ZSgtMzQ4NS43IDI1NjIuNikgc2NhbGUoLjI2NDU4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzNjM2MzYyIvPjxzdG9wIG9mZnNldD0iMSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGZpbGw9InVybCgjYSkiIHN0cm9rZT0iI2U2ZTZlNiIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjEiIGQ9Ik0yNSA2LjU1Yy0uODc4IDAtMS42NTQuMjktMi4yNjEuOTAzLS42NDEuNjEyLS45NDYgMS4zMjEtLjk0NiAyLjE1OSAwIDEuMjI1LjU3NCAyLjEyNyAxLjc1NiAyLjc0LTIuOTcgMy4yODUtOC43MDggNS44MjEtOC44MTEgMTAuODI3LjAxIDIuNjc1IDEuNDY2IDQuNzY0IDMuMzA4IDYuOGwtMS4xMTQgNS44MzNjMS42OTcuNTQyIDMuMDkuOTQyIDQuODI3IDEuMTI4LTMuODgyIDQuNTc2LTEwLjc4Ny0xLjc0LTE1LjIwOSAyLjkzM2wyLjMzIDMuNTc3YzUuNTkzLTMuOTYyIDEzLjM3NSAzLjY3MyAxNi4xMi0zLjk2MiAyLjc0NiA3LjYzNSAxMC41MjggMCAxNi4xMiAzLjk2MmwyLjMzLTMuNTc3Yy00LjQyMi00LjY3My0xMS4zMjcgMS42NDMtMTUuMjA5LTIuOTMzIDEuNzM4LS4xODYgMy4xMy0uNTg2IDQuODI4LTEuMTI4bC0xLjExNS01LjgzM2MxLjg0My0yLjAzNiAzLjMwMi00LjEyNSAzLjMwOS02LjgtLjEwMy01LjAwNi01Ljg0Mi03LjU0Mi04LjgxMS0xMC44MjggMS4xODEtLjYxMiAxLjc1NS0xLjUxNCAxLjc1NS0yLjc0IDAtLjgzNy0uMzA0LTEuNTQ2LS45NDUtMi4xNTgtLjYwOC0uNjEyLTEuMzg0LS45MDMtMi4yNjEtLjkwM3oiIGZpbHRlcj0idXJsKCNiKSIvPjxlbGxpcHNlIGN4PSIyNzIwLjMiIGN5PSItMjcxLjQiIGZpbGw9IiNlNmU2ZTYiIGNsYXNzPSJzdDE1IiBmaWx0ZXI9InVybCgjYykiIHJ4PSIxNi4zIiByeT0iMi41IiB0cmFuc2Zvcm09Im1hdHJpeCguMzMyMzIgMCAwIC4yNDk5OCAtODc5LjAxIDEwMi40NykiLz48ZWxsaXBzZSBjeD0iMjUiIGN5PSI5LjYxMiIgZmlsbD0iI2U2ZTZlNiIgY2xhc3M9InN0MTUiIHJ4PSIxLjE0IiByeT0iMS4xNDciLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiNlNmU2ZTYiIHN0cm9rZS13aWR0aD0iMS40IiBkPSJNMjEuMzMzIDIzLjI2Nmg3LjMzM00yNSAxOS45MzJ2Ni43NTIiLz48L3N2Zz4=')} -.is2d .rook.black {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MG1tIiBoZWlnaHQ9IjUwbW0iIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNDUwMS41IiB4Mj0iNDU5NC42IiB5MT0iLTU3Mi40IiB5Mj0iLTU3Mi40IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4zNDIwOCAwIDAgLjI4MzcgLTE1MzAuOCAxODcuMzkpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM2MzYzNjIi8+PHN0b3Agb2Zmc2V0PSIxIi8+PC9saW5lYXJHcmFkaWVudD48ZmlsdGVyIGlkPSJiIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPjxmZUZsb29kIGZsb29kLWNvbG9yPSIjMDAwIiBmbG9vZC1vcGFjaXR5PSIuNDk4IiByZXN1bHQ9ImZsb29kIi8+PGZlQ29tcG9zaXRlIGluPSJmbG9vZCIgaW4yPSJTb3VyY2VHcmFwaGljIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iY29tcG9zaXRlMSIvPjxmZUdhdXNzaWFuQmx1ciBpbj0iY29tcG9zaXRlMSIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249Ii4zIi8+PGZlT2Zmc2V0IGR4PSIxIiBkeT0iMSIgcmVzdWx0PSJvZmZzZXQiLz48ZmVDb21wb3NpdGUgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0ib2Zmc2V0IiByZXN1bHQ9ImNvbXBvc2l0ZTIiLz48L2ZpbHRlcj48L2RlZnM+PHBhdGggZmlsbD0idXJsKCNhKSIgc3Ryb2tlPSIjZTZlNmU2IiBzdHJva2Utd2lkdGg9IjEuMTQ0IiBkPSJNMjEuOTMyIDYuNTQ2VjkuNDhoLTQuMDkxVjYuODkyaC01Ljc5NnY3Ljk3NWw0LjUzMyAzLjE0MnYxMi41NjdsLTMuODUgMi40ODZ2NS4yMTNIOS42NTh2NS4xNzloMzAuNjgydi01LjE4aC0zLjA2OHYtNS4yMTJsLTMuODUtMi40ODZWMTguMDQzbDQuNTMyLTMuMjExdi03Ljk0aC01Ljc5NnYyLjU4N2gtNC40MzJWNi41NDZIMjQuODN6IiBjbGFzcz0ic3QxNCIgZmlsdGVyPSJ1cmwoI2IpIiB0cmFuc2Zvcm09Im1hdHJpeCgxLjAwNTUgMCAwIC45MTk4IC0uMiAzLjUpIi8+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTZlNmU2IiBzdHJva2Utd2lkdGg9IjEuNCIgZD0iTTE4LjggMzEuNGgxMS45OThNMTguOCAyMGgxMS45OTgiLz48L3N2Zz4=')} -.is2d .queen.black {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MG1tIiBoZWlnaHQ9IjUwbW0iIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGZpbHRlciBpZD0iYyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249IjAuMDEgMC4wMSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+PGZlRmxvb2QgZmxvb2QtY29sb3I9IiMwMDAiIGZsb29kLW9wYWNpdHk9Ii40OTgiIHJlc3VsdD0iZmxvb2QiLz48ZmVDb21wb3NpdGUgaW49ImZsb29kIiBpbjI9IlNvdXJjZUdyYXBoaWMiIG9wZXJhdG9yPSJpbiIgcmVzdWx0PSJjb21wb3NpdGUxIi8+PGZlR2F1c3NpYW5CbHVyIGluPSJjb21wb3NpdGUxIiByZXN1bHQ9ImJsdXIiIHN0ZERldmlhdGlvbj0iLjMiLz48ZmVPZmZzZXQgZHg9IjEiIGR5PSIxIiByZXN1bHQ9Im9mZnNldCIvPjxmZUNvbXBvc2l0ZSBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJvZmZzZXQiIHJlc3VsdD0iY29tcG9zaXRlMiIvPjwvZmlsdGVyPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeDE9Ii03MS42MzciIHgyPSItMzAuNjc4IiB5MT0iLTgzLjMyNSIgeTI9Ii04My4zMjUiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjk3NjQ0IDAgMCAuOTkyODYgNzQuOTUyIDEwNy43MykiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzYzNjM2MiLz48c3RvcCBvZmZzZXQ9IjEiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cGF0aCBmaWxsPSJ1cmwoI2EpIiBzdHJva2U9IiNlNmU2ZTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjEiIGQ9Ik0yNC45OTQgNi41NDl2MGMtMS41NjguMDA2LTIuODM1IDEuMTgxLTIuODM2IDIuNjMuMDAyIDEuMTkzLjg3MyAyLjIzNSAyLjEyMiAyLjUzOS0uNjg4IDQuNDUtMS45NjcgOS43MjYtMi42MzQgMTQuMTEybC00LjA3LTEyLjkyN2MuOTY4LS40NDQgMS41OC0xLjM1NiAxLjU4LTIuMzUzIDAtMS40NTMtMS4yNzUtMi42My0yLjg0Ny0yLjYzcy0yLjg0NyAxLjE3Ny0yLjg0NyAyLjYzYy4wMDIgMS4yMDUuODkgMi4yNTUgMi4xNTcgMi41NDdsLS40NCAxMy4yNTgtNS40ODItMTAuNjExYy45NTEtLjQ1IDEuNTUtMS4zNTQgMS41NS0yLjM0IDAtMS40NTMtMS4yNzUtMi42My0yLjg0Ny0yLjYzLTEuNTczIDAtMi44NDcgMS4xNzctMi44NDcgMi42MyAwIDEuMzM0IDEuMDg0IDIuNDU2IDIuNTE5IDIuNjFsMi43NiAxNi41MDcgNC4wNSA1LjI1OC0xLjAwNCAzLjYzNGMtLjA0Mi42NTYgNC44NDggMi4wMjggMTEuMTIyIDIuMDQgNi4yNzMtLjAxMiAxMS4xNjQtMS4zODQgMTEuMTIyLTIuMDRsLTEuMDA1LTMuNjM0IDQuMDUtNS4yNTggMi43Ni0xNi41MDdjMS40MzUtLjE1NCAyLjUxOS0xLjI3NiAyLjUyLTIuNjEgMC0xLjQ1My0xLjI3NS0yLjYzLTIuODQ3LTIuNjMtMS41NzMgMC0yLjg0NyAxLjE3Ny0yLjg0NyAyLjYzIDAgLjk4Ni41OTggMS44OSAxLjU1IDIuMzRsLTUuNDg0IDEwLjYxLS40MzktMTMuMjU3YzEuMjY2LS4yOTIgMi4xNTUtMS4zNDIgMi4xNTctMi41NDcgMC0xLjQ1My0xLjI3NS0yLjYzLTIuODQ3LTIuNjNzLTIuODQ3IDEuMTc3LTIuODQ3IDIuNjNjMCAuOTk3LjYxMiAxLjkwOSAxLjU4IDIuMzUzbC00LjA3IDEyLjkyN2MtLjY2Ny00LjM4Ni0xLjk0Ni05LjY2Mi0yLjYzNC0xNC4xMTIgMS4yNDktLjMwNCAyLjEyLTEuMzQ2IDIuMTIyLTIuNTQgMC0xLjQ0OC0xLjI2OC0yLjYyMy0yLjgzNi0yLjYyOXYwaC0uMDExeiIgZmlsdGVyPSJ1cmwoI2IpIiBzdHlsZT0icGFpbnQtb3JkZXI6bm9ybWFsIi8+PGVsbGlwc2UgY3g9IjQ3MDguNyIgY3k9Ii0yNTE3LjYiIGZpbGw9IiNlNmU2ZTYiIGNsYXNzPSJzdDE1IiBmaWx0ZXI9InVybCgjYykiIHJ4PSIzMi4xMjYiIHJ5PSIyLjg0NCIgdHJhbnNmb3JtPSJtYXRyaXgoLjI1OTM5IDAgMCAuMjkyOTggLTExOTYuNCA3NzguMTIpIi8+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTZlNmU2IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik0xNS4xNzIgMzQuMDc2czIuNy0xLjI0OSA5LjgwMi0xLjI1NmM3LjEwMy0uMDEgOS44MDEgMS4yNTYgOS44MDEgMS4yNTYiIHN0eWxlPSJwYWludC1vcmRlcjpzdHJva2UgZmlsbCBtYXJrZXJzIi8+PC9zdmc+')} -.is2d .king.black {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MG1tIiBoZWlnaHQ9IjUwbW0iIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGZpbHRlciBpZD0iYyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249IjAuMDEgMC4wMSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+PGZlRmxvb2QgZmxvb2QtY29sb3I9IiMwMDAiIGZsb29kLW9wYWNpdHk9Ii40OTgiIHJlc3VsdD0iZmxvb2QiLz48ZmVDb21wb3NpdGUgaW49ImZsb29kIiBpbjI9IlNvdXJjZUdyYXBoaWMiIG9wZXJhdG9yPSJpbiIgcmVzdWx0PSJjb21wb3NpdGUxIi8+PGZlR2F1c3NpYW5CbHVyIGluPSJjb21wb3NpdGUxIiByZXN1bHQ9ImJsdXIiIHN0ZERldmlhdGlvbj0iLjYiLz48ZmVPZmZzZXQgZHg9IjEuNiIgZHk9IjEuNCIgcmVzdWx0PSJvZmZzZXQiLz48ZmVDb21wb3NpdGUgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0ib2Zmc2V0IiByZXN1bHQ9ImNvbXBvc2l0ZTIiLz48L2ZpbHRlcj48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSIyOTg2LjQiIHgyPSIzMTI4LjQiIHkxPSIxNjIzLjgiIHkyPSIxNjIzLjgiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjI3MTQxIDAgMCAuMjcyMTggLTgwNC44MSAtNDE3LjQ1KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzNjM2MzYyIvPjxzdG9wIG9mZnNldD0iMSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGZpbGw9InVybCgjYSkiIHN0cm9rZT0iI2U2ZTZlNiIgc3Ryb2tlLXdpZHRoPSIxLjEiIGQ9Ik0yMy4yODMgNS41NXYzLjIzOGgtMy4zNjR2Mi45MmgzLjM2NHYxLjc1OGMtMy4zNjggMi4xMjctMi45OTYgNS43NC0yLjk5NiA1Ljc0QzkuMjc4IDEwLjY5LS4zODUgMjYuNzcgMTIuMzQzIDMyLjI2djguNzM0YzAgLjk1IDUuNjY3IDIuNDU2IDEyLjY1NyAyLjQ1NnMxMi42NTctMS41MDYgMTIuNjU3LTIuNDU2VjMyLjI2YzEyLjcyOC01LjQ5IDMuMDY2LTIxLjU2OS03Ljk0My0xMy4wNTMgMCAwIC4zNzItMy42MTMtMi45OTYtNS43NHYtMS43NThoMy4zNjR2LTIuOTJoLTMuMzY0VjUuNTUxaC0xLjcxN3oiIGZpbHRlcj0idXJsKCNiKSIvPjxlbGxpcHNlIGN4PSI3MS4wNzciIGN5PSIxMzEuNTQiIGZpbGw9IiNlNmU2ZTYiIGNsYXNzPSJzdDE1IiBmaWx0ZXI9InVybCgjYykiIHJ4PSIzMi4xMjYiIHJ5PSIyLjg0NCIgdHJhbnNmb3JtPSJtYXRyaXgoLjI4NTMzIDAgMCAuMzIyMyA0LjcyIC0xLjk4KSIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2U2ZTZlNiIgc3Ryb2tlLXdpZHRoPSIxLjQiIGQ9Ik0yNy4wMzIgMzAuMjY3YzEuNDktMTIuMTAyIDExLjk0My0xMi40NDEgMTMuMzY0LTcuMzggMS40MiA1LjA2Mi00LjczNiA3LjM4LTQuNzM2IDcuMzhzLTQuODc1LS42MzgtMTAuNjYtLjYzOC0xMC42Ni42MzgtMTAuNjYuNjM4LTYuMTU2LTIuMzE4LTQuNzM1LTcuMzggMTEuODc0LTQuNzIyIDEzLjM2NCA3LjM4Ii8+PC9zdmc+')} +.is2d .pawn.white {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNDEyNy4zIiB4Mj0iNDIzNS43IiB5MT0iLTI1NTguNCIgeTI9Ii0yNTU4LjQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjI3Njc3IDAgMCAuMjc1NTUgLTExMzIuMyA3MzEuOTYpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZmZmIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZTZlNmU2Ii8+PC9saW5lYXJHcmFkaWVudD48ZmlsdGVyIGlkPSJiIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPjxmZUZsb29kIGZsb29kLWNvbG9yPSIjMDAwIiBmbG9vZC1vcGFjaXR5PSIuNSIgcmVzdWx0PSJmbG9vZCIvPjxmZUNvbXBvc2l0ZSBpbj0iZmxvb2QiIGluMj0iU291cmNlR3JhcGhpYyIgb3BlcmF0b3I9ImluIiByZXN1bHQ9ImNvbXBvc2l0ZTEiLz48ZmVHYXVzc2lhbkJsdXIgaW49ImNvbXBvc2l0ZTEiIHJlc3VsdD0iYmx1ciIgc3RkRGV2aWF0aW9uPSIuMyIvPjxmZU9mZnNldCBkeD0iMSIgZHk9IjEiIHJlc3VsdD0ib2Zmc2V0Ii8+PGZlQ29tcG9zaXRlIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9Im9mZnNldCIgcmVzdWx0PSJjb21wb3NpdGUyIi8+PC9maWx0ZXI+PC9kZWZzPjxwYXRoIGZpbGw9InVybCgjYSkiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjE0IiBkPSJNMjUuMDIgNDMuNGgtMTMuOWMtMi41NS01Ljg4IDQuMjEtMTEuMzQgOC45Ny0xMy4yLTUuNjktMy4xNS0yLjYtMTEuMjEgMi4yNy0xMS44Ny0xLjE2LS43Ni0xLjc0LTIuMzktMS43NC0zLjcgMC0xLjA5LjQ2LTIuMDcgMS4yNy0yLjgzYTQuNDMgNC40MyAwIDAgMSAzLjEzLTEuMmMxLjE2IDAgMi4yLjQ0IDMuMTQgMS4yYTMuODQgMy44NCAwIDAgMSAxLjI3IDIuODNjMCAxLjMxLS41OCAyLjk0LTEuNzQgMy43IDUuMzQgMi4wNyA3LjI2IDkuNyAyLjI3IDExLjg4IDYuNSAyLjI5IDExLjA2IDguMDcgOC45NyAxMy4yeiIgY2xhc3M9InN0MzEiIGZpbHRlcj0idXJsKCNiKSIgdHJhbnNmb3JtPSJtYXRyaXgoLjk2NjU4IDAgMCAuOTcyNDUgLjgzIDEuMjQpIi8+PC9zdmc+')} +.is2d .knight.white {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iLTQ1NS4zOSIgeDI9Ii00MTkuNDEiIHkxPSItMzM4LjIzIiB5Mj0iLTMzOC4yMyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZjZmNmOCIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2U3ZTdlMyIvPjwvbGluZWFyR3JhZGllbnQ+PGZpbHRlciBpZD0iYiIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVGbG9vZCBmbG9vZC1jb2xvcj0iIzAwMCIgZmxvb2Qtb3BhY2l0eT0iLjUiIHJlc3VsdD0iZmxvb2QiLz48ZmVDb21wb3NpdGUgaW49ImZsb29kIiBpbjI9IlNvdXJjZUdyYXBoaWMiIG9wZXJhdG9yPSJpbiIgcmVzdWx0PSJjb21wb3NpdGUxIi8+PGZlR2F1c3NpYW5CbHVyIGluPSJjb21wb3NpdGUxIiByZXN1bHQ9ImJsdXIiIHN0ZERldmlhdGlvbj0iLjYiLz48ZmVPZmZzZXQgZHg9IjEuNiIgZHk9IjEuNCIgcmVzdWx0PSJvZmZzZXQiLz48ZmVDb21wb3NpdGUgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0ib2Zmc2V0IiByZXN1bHQ9ImNvbXBvc2l0ZTIiLz48L2ZpbHRlcj48L2RlZnM+PHBhdGggZmlsbD0idXJsKCNhKSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjEuMSIgZD0iTS00NDMuOTItMzMyLjk1YzIuMS0xLjIgMy4zMi0xLjE4IDUuNDYtMi4yLjIzIDcuNDMtOS45IDcuNDYtOC4wOCAxNS4zNmgyNi40czMuMS0zMi4yNy0xNi43OC0zMy42MmMwIDAtMS45MS0zLjYtMy45My0zLjI1IDAgMC0xLjA2Ljg0LS40NSAzLjJsLTIuMy43NXMtMy4yMi0yLjA3LTQuMTMtMS4yN2MtLjg2LjM3IDEuMSAzLjI4IDEuODggMy45OS0uOCAxLjE0LTguNTUgMTIuMS04Ljk2IDE1LjY4LS4yNyAyLjI4IDIuMDIgMy41MiAzLjcxIDQuMTIuOTcuMzQgMS43NC40NyAxLjc0LjQ3IDEuNDItLjI1IDMuMzQtMi4wMyA1LjQ0LTMuMjN6IiBmaWx0ZXI9InVybCgjYikiIHRyYW5zZm9ybT0ibWF0cml4KDEuMDAwOCAwIDAgMS4wMDAxIDQ2Mi43NSAzNjMuMjYpIi8+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMS4xIiBkPSJNMjMuOTQgMjguMDlzNC40My0xLjg3IDQuMjItNS44NCIvPjxwYXRoIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuNCIgZD0iTTE5LjEgMTguNDdzLjYtMS44NCAzLjQ2LTIuMyIvPjxlbGxpcHNlIGN4PSIyMS4wMyIgY3k9IjE4IiBwYWludC1vcmRlcj0ibWFya2VycyBmaWxsIHN0cm9rZSIgcng9IjEuMjQiIHJ5PSIxLjE3Ii8+PHBhdGggZmlsbD0iI2ZmZiIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMS40IiBkPSJNOS4xNyAyOS4yNHMuMjUtLjY4LjkyLTEuMTIiLz48cGF0aCBmaWxsPSIjZmZmIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjIiIGQ9Ik0xMS42NCAzMi4yOGMuNjktLjg4IDEuNTgtMS4zMiAyLjM4LTEuOTUiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMS40IiBkPSJNMzAuOCAxNC44N2M0LjMxIDIuNjQgOC40NyA5LjI1IDguMTIgMjYuMDgiLz48L3N2Zz4=')} +.is2d .bishop.white {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGZpbHRlciBpZD0iYyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249IjAuMDEgMC4wMSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+PGZlRmxvb2QgZmxvb2QtY29sb3I9IiMwMDAiIGZsb29kLW9wYWNpdHk9Ii41IiByZXN1bHQ9ImZsb29kIi8+PGZlQ29tcG9zaXRlIGluPSJmbG9vZCIgaW4yPSJTb3VyY2VHcmFwaGljIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iY29tcG9zaXRlMSIvPjxmZUdhdXNzaWFuQmx1ciBpbj0iY29tcG9zaXRlMSIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249Ii4zIi8+PGZlT2Zmc2V0IGR4PSIxIiBkeT0iMSIgcmVzdWx0PSJvZmZzZXQiLz48ZmVDb21wb3NpdGUgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0ib2Zmc2V0IiByZXN1bHQ9ImNvbXBvc2l0ZTIiLz48L2ZpbHRlcj48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSIxMzE5NyIgeDI9IjEzMzQxIiB5MT0iLTk1OTEiIHkyPSItOTU5MSIgZ3JhZGllbnRUcmFuc2Zvcm09InRyYW5zbGF0ZSgtMzQ4NS43IDI1NjIuNikgc2NhbGUoLjI2NDU4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZmZiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2U2ZTZlNiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGZpbGw9InVybCgjYSkiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjEiIGQ9Ik0yNSA2LjU1Yy0uODggMC0xLjY1LjI5LTIuMjYuOWEyLjg2IDIuODYgMCAwIDAtLjk1IDIuMTZjMCAxLjIzLjU4IDIuMTMgMS43NiAyLjc0LTIuOTcgMy4yOS04LjcgNS44Mi04LjgxIDEwLjgzIDAgMi42NyAxLjQ2IDQuNzYgMy4zIDYuOGwtMS4xIDUuODNjMS42OS41NCAzLjA4Ljk0IDQuODIgMS4xMy0zLjg4IDQuNTgtMTAuNzktMS43NC0xNS4yMSAyLjkzbDIuMzMgMy41OGM1LjYtMy45NiAxMy4zOCAzLjY3IDE2LjEyLTMuOTYgMi43NSA3LjYzIDEwLjUzIDAgMTYuMTIgMy45NmwyLjMzLTMuNThjLTQuNDItNC42Ny0xMS4zMyAxLjY1LTE1LjItMi45M2EyMy4xIDIzLjEgMCAwIDAgNC44Mi0xLjEzbC0xLjEyLTUuODNjMS44NS0yLjA0IDMuMy00LjEzIDMuMzEtNi44LS4xLTUtNS44NC03LjU0LTguOC0xMC44MyAxLjE3LS42MSAxLjc1LTEuNTEgMS43NS0yLjc0IDAtLjg0LS4zLTEuNTUtLjk1LTIuMTYtLjYtLjYxLTEuMzgtLjktMi4yNi0uOXoiIGZpbHRlcj0idXJsKCNiKSIvPjxlbGxpcHNlIGN4PSIyNzIwLjMiIGN5PSItMjcxLjQiIGNsYXNzPSJzdDE1IiBmaWx0ZXI9InVybCgjYykiIHJ4PSIxNi4zIiByeT0iMi41IiB0cmFuc2Zvcm09Im1hdHJpeCguMzMyMzIgMCAwIC4yNDk5OCAtODc5LjAxIDEwMi40NykiLz48ZWxsaXBzZSBjeD0iMjUiIGN5PSI5LjYxIiBjbGFzcz0ic3QxNSIgcng9IjEuMTQiIHJ5PSIxLjE1Ii8+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjEuNCIgZD0iTTIxLjMzIDIzLjI3aDcuMzRNMjUgMTkuOTN2Ni43NSIvPjwvc3ZnPg==')} +.is2d .rook.white {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNDUwMS41IiB4Mj0iNDU5NC42IiB5MT0iLTU3Mi40IiB5Mj0iLTU3Mi40IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4zNDIwOCAwIDAgLjI4MzcgLTE1MzAuOCAxODcuMzkpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZmZmIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZTZlNmU2Ii8+PC9saW5lYXJHcmFkaWVudD48ZmlsdGVyIGlkPSJiIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPjxmZUZsb29kIGZsb29kLWNvbG9yPSIjMDAwIiBmbG9vZC1vcGFjaXR5PSIuNSIgcmVzdWx0PSJmbG9vZCIvPjxmZUNvbXBvc2l0ZSBpbj0iZmxvb2QiIGluMj0iU291cmNlR3JhcGhpYyIgb3BlcmF0b3I9ImluIiByZXN1bHQ9ImNvbXBvc2l0ZTEiLz48ZmVHYXVzc2lhbkJsdXIgaW49ImNvbXBvc2l0ZTEiIHJlc3VsdD0iYmx1ciIgc3RkRGV2aWF0aW9uPSIuMyIvPjxmZU9mZnNldCBkeD0iMSIgZHk9IjEiIHJlc3VsdD0ib2Zmc2V0Ii8+PGZlQ29tcG9zaXRlIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9Im9mZnNldCIgcmVzdWx0PSJjb21wb3NpdGUyIi8+PC9maWx0ZXI+PC9kZWZzPjxwYXRoIGZpbGw9InVybCgjYSkiIHN0cm9rZT0iIzAxMDEwMSIgc3Ryb2tlLXdpZHRoPSIxLjE0IiBkPSJNMjEuOTMgNi41NXYyLjkzaC00LjA5VjYuODloLTUuOHY3Ljk4TDE2LjU4IDE4djEyLjU3bC0zLjg1IDIuNDh2NS4yMUg5LjY2djUuMThoMzAuNjh2LTUuMThoLTMuMDd2LTUuMmwtMy44NS0yLjVWMTguMDVsNC41My0zLjJWNi44OGgtNS44djIuNTloLTQuNDJWNi41NWgtMi45eiIgY2xhc3M9InN0MTQiIGZpbHRlcj0idXJsKCNiKSIgdHJhbnNmb3JtPSJtYXRyaXgoMS4wMDU1IDAgMCAuOTE5OCAtLjE0IDMuNSkiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS40IiBkPSJNMTguODMgMzEuNDRoMTJNMTguODMgMjBoMTIiLz48L3N2Zz4=')} +.is2d .queen.white {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGZpbHRlciBpZD0iYyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249IjAuMDEgMC4wMSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+PGZlRmxvb2QgZmxvb2QtY29sb3I9IiMwMDAiIGZsb29kLW9wYWNpdHk9Ii41IiByZXN1bHQ9ImZsb29kIi8+PGZlQ29tcG9zaXRlIGluPSJmbG9vZCIgaW4yPSJTb3VyY2VHcmFwaGljIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iY29tcG9zaXRlMSIvPjxmZUdhdXNzaWFuQmx1ciBpbj0iY29tcG9zaXRlMSIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249Ii4zIi8+PGZlT2Zmc2V0IGR4PSIxIiBkeT0iMSIgcmVzdWx0PSJvZmZzZXQiLz48ZmVDb21wb3NpdGUgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0ib2Zmc2V0IiByZXN1bHQ9ImNvbXBvc2l0ZTIiLz48L2ZpbHRlcj48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSItNzEuNjQiIHgyPSItMzAuNjgiIHkxPSItODMuMzIiIHkyPSItODMuMzIiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjk3NjQzIDAgMCAuOTkyODcgNzQuOTUgMTA3LjczKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZmZiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2U2ZTZlNiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGZpbGw9InVybCgjYSkiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuMSIgZD0iTTI1IDYuNTV2MGMtMS41NyAwLTIuODQgMS4xOC0yLjg0IDIuNjMgMCAxLjIuODcgMi4yMyAyLjEyIDIuNTQtLjY5IDQuNDUtMS45NyA5LjcyLTIuNjMgMTQuMTFMMTcuNTggMTIuOWEyLjYyIDIuNjIgMCAwIDAgMS41OC0yLjM1YzAtMS40NS0xLjI4LTIuNjMtMi44NS0yLjYzcy0yLjg1IDEuMTgtMi44NSAyLjYzYzAgMS4yLjkgMi4yNSAyLjE2IDIuNTVsLS40NCAxMy4yNS01LjQ4LTEwLjZhMi42MiAyLjYyIDAgMCAwIDEuNTUtMi4zNWMwLTEuNDUtMS4yOC0yLjYzLTIuODUtMi42M3MtMi44NSAxLjE4LTIuODUgMi42M2MwIDEuMzQgMS4wOSAyLjQ2IDIuNTIgMi42MWwyLjc2IDE2LjUxIDQuMDUgNS4yNi0xIDMuNjNjLS4wNC42NiA0Ljg1IDIuMDMgMTEuMTIgMi4wNCA2LjI3LS4wMSAxMS4xNi0xLjM4IDExLjEyLTIuMDRsLTEtMy42MyA0LjA1LTUuMjYgMi43Ni0xNi41YTIuNzQgMi43NCAwIDAgMCAyLjUyLTIuNjJjMC0xLjQ1LTEuMjgtMi42My0yLjg1LTIuNjNzLTIuODUgMS4xOC0yLjg1IDIuNjNjMCAuOTkuNiAxLjkgMS41NSAyLjM0bC01LjQ4IDEwLjYxLS40NC0xMy4yNWEyLjY4IDIuNjggMCAwIDAgMi4xNi0yLjU1YzAtMS40NS0xLjI4LTIuNjMtMi44NS0yLjYzcy0yLjg1IDEuMTgtMi44NSAyLjYzYzAgMSAuNjEgMS45IDEuNTggMi4zNWwtNC4wNyAxMi45M2MtLjY3LTQuMzktMS45NC05LjY2LTIuNjMtMTQuMTFhMi42OCAyLjY4IDAgMCAwIDIuMTItMi41NGMwLTEuNDUtMS4yNy0yLjYzLTIuODQtMi42M3YwaDB6IiBmaWx0ZXI9InVybCgjYikiLz48ZWxsaXBzZSBjeD0iNDcwOC43IiBjeT0iLTI1MTcuNiIgY2xhc3M9InN0MTUiIGZpbHRlcj0idXJsKCNjKSIgcng9IjMyLjEzIiByeT0iMi44NCIgdHJhbnNmb3JtPSJtYXRyaXgoLjI1OTM5IDAgMCAuMjkyOTggLTExOTYuNCA3NzguMTIpIi8+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik0xNS4xNyAzNC4wOHMyLjctMS4yNSA5LjgtMS4yNmM3LjEtLjAxIDkuOCAxLjI2IDkuOCAxLjI2IiBwYWludC1vcmRlcj0ic3Ryb2tlIGZpbGwgbWFya2VycyIvPjwvc3ZnPg==')} +.is2d .king.white {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGZpbHRlciBpZD0iYyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249IjAuMDEgMC4wMSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+PGZlRmxvb2QgZmxvb2QtY29sb3I9IiMwMDAiIGZsb29kLW9wYWNpdHk9Ii41IiByZXN1bHQ9ImZsb29kIi8+PGZlQ29tcG9zaXRlIGluPSJmbG9vZCIgaW4yPSJTb3VyY2VHcmFwaGljIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iY29tcG9zaXRlMSIvPjxmZUdhdXNzaWFuQmx1ciBpbj0iY29tcG9zaXRlMSIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249Ii42Ii8+PGZlT2Zmc2V0IGR4PSIxLjYiIGR5PSIxLjQiIHJlc3VsdD0ib2Zmc2V0Ii8+PGZlQ29tcG9zaXRlIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9Im9mZnNldCIgcmVzdWx0PSJjb21wb3NpdGUyIi8+PC9maWx0ZXI+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iMjk4Ni40IiB4Mj0iMzEyOC40IiB5MT0iMTYyMy44IiB5Mj0iMTYyMy44IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4yNzE0MSAwIDAgLjI3MjE4IC04MDQuODEgLTQxNy40NSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZmYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNlNmU2ZTYiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cGF0aCBmaWxsPSJ1cmwoI2EpIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMS4xIiBkPSJNMjMuMjggNS41NXYzLjI0aC0zLjM2djIuOTJoMy4zNnYxLjc2Yy0zLjM2IDIuMTItMyA1Ljc0LTMgNS43NC0xMS04LjUyLTIwLjY3IDcuNTYtNy45NCAxMy4wNXY4LjczYzAgLjk1IDUuNjcgMi40NiAxMi42NiAyLjQ2czEyLjY2LTEuNSAxMi42Ni0yLjQ2di04LjczYzEyLjcyLTUuNDkgMy4wNi0yMS41Ny03Ljk1LTEzLjA1IDAgMCAuMzgtMy42Mi0zLTUuNzRWMTEuN2gzLjM3VjguNzloLTMuMzZWNS41NUgyNXoiIGZpbHRlcj0idXJsKCNiKSIvPjxlbGxpcHNlIGN4PSI3MS4wOCIgY3k9IjEzMS41NCIgY2xhc3M9InN0MTUiIGZpbHRlcj0idXJsKCNjKSIgcng9IjMyLjEzIiByeT0iMi44NCIgdHJhbnNmb3JtPSJtYXRyaXgoLjI4NTMzIDAgMCAuMzIyMyA0LjcyIC0xLjk4KSIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIxLjQiIGQ9Ik0yNy4wMyAzMC4yN2MxLjUtMTIuMSAxMS45NC0xMi40NCAxMy4zNy03LjM4IDEuNDIgNS4wNi00Ljc0IDcuMzgtNC43NCA3LjM4cy00Ljg3LS42NC0xMC42Ni0uNjQtMTAuNjYuNjQtMTAuNjYuNjQtNi4xNi0yLjMyLTQuNzMtNy4zOCAxMS44Ny00LjczIDEzLjM2IDcuMzgiLz48L3N2Zz4=')} +.is2d .pawn.black {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNDEyNy4yIiB4Mj0iNDIzNS43IiB5MT0iLTI1NTguMyIgeTI9Ii0yNTU4LjMiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjI2NzQ5IDAgMCAuMjY3OTkgLTEwOTMuNSA3MTMuMTEpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM2MzYzNjIi8+PHN0b3Agb2Zmc2V0PSIxIi8+PC9saW5lYXJHcmFkaWVudD48ZmlsdGVyIGlkPSJiIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPjxmZUZsb29kIGZsb29kLWNvbG9yPSIjMDAwIiBmbG9vZC1vcGFjaXR5PSIuNDk4IiByZXN1bHQ9ImZsb29kIi8+PGZlQ29tcG9zaXRlIGluPSJmbG9vZCIgaW4yPSJTb3VyY2VHcmFwaGljIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iY29tcG9zaXRlMSIvPjxmZUdhdXNzaWFuQmx1ciBpbj0iY29tcG9zaXRlMSIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249Ii4zIi8+PGZlT2Zmc2V0IGR4PSIxIiBkeT0iMSIgcmVzdWx0PSJvZmZzZXQiLz48ZmVDb21wb3NpdGUgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0ib2Zmc2V0IiByZXN1bHQ9ImNvbXBvc2l0ZTIiLz48L2ZpbHRlcj48L2RlZnM+PHBhdGggZmlsbD0idXJsKCNhKSIgc3Ryb2tlPSIjZTZlNmU2IiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuMSIgZD0iTTI1LjAxOSA0My40NUgxMS41OGMtMi40NjYtNS43MjQgNC4wNzItMTEuMDMgOC42NjgtMTIuODMyLTUuNDkzLTMuMDc0LTIuNTE1LTEwLjkxMSAyLjE5Mi0xMS41NDctMS4xMi0uNzQyLTEuNjgxLTIuMzI3LTEuNjgxLTMuNiAwLTEuMDYuNDQ4LTIuMDEzIDEuMjMzLTIuNzU1Ljc4NS0uNzQyIDEuNzkzLTEuMTY2IDMuMDI2LTEuMTY2IDEuMTIxIDAgMi4xMy40MjQgMy4wMjYgMS4xNjYuNzg1Ljc0MiAxLjIzMyAxLjY5NiAxLjIzMyAyLjc1NiAwIDEuMjcyLS41NiAyLjg1Ny0xLjY4MSAzLjU5OSA1LjE1NiAyLjAxNCA3LjAxMiA5LjQyNyAyLjE5MyAxMS41NDcgNi4yNzYgMi4yMjYgMTAuNjg1IDcuODUgOC42NjcgMTIuODMyeiIgY2xhc3M9InN0MzEiIGZpbHRlcj0idXJsKCNiKSIvPjwvc3ZnPg==')} +.is2d .knight.black {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iLTQ1NS4zOSIgeDI9Ii00MTkuNDEiIHkxPSItMzM4LjIzIiB5Mj0iLTMzOC4yMyIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLjAwMDggMCAwIDEuMDAwMSA0NjIuNzUgMzYzLjI2KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzNjM2MzYyIvPjxzdG9wIG9mZnNldD0iMSIvPjwvbGluZWFyR3JhZGllbnQ+PGZpbHRlciBpZD0iYiIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVGbG9vZCBmbG9vZC1jb2xvcj0iIzAwMCIgZmxvb2Qtb3BhY2l0eT0iLjUiIHJlc3VsdD0iZmxvb2QiLz48ZmVDb21wb3NpdGUgaW49ImZsb29kIiBpbjI9IlNvdXJjZUdyYXBoaWMiIG9wZXJhdG9yPSJpbiIgcmVzdWx0PSJjb21wb3NpdGUxIi8+PGZlR2F1c3NpYW5CbHVyIGluPSJjb21wb3NpdGUxIiByZXN1bHQ9ImJsdXIiIHN0ZERldmlhdGlvbj0iLjYiLz48ZmVPZmZzZXQgZHg9IjEuNiIgZHk9IjEuNCIgcmVzdWx0PSJvZmZzZXQiLz48ZmVDb21wb3NpdGUgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0ib2Zmc2V0IiByZXN1bHQ9ImNvbXBvc2l0ZTIiLz48L2ZpbHRlcj48L2RlZnM+PHBhdGggZmlsbD0idXJsKCNhKSIgc3Ryb2tlPSIjZTZlNmU2IiBzdHJva2Utd2lkdGg9IjEuMSIgZD0iTTE4LjQ3IDMwLjI5YzIuMS0xLjIgMy4zMy0xLjE5IDUuNDctMi4yLjIyIDcuNDItOS45IDcuNDUtOC4xIDE1LjM2aDI2LjQzczMuMS0zMi4yNy0xNi43OS0zMy42M2MwIDAtMS45Mi0zLjYtMy45My0zLjI1IDAgMC0xLjA2Ljg0LS40NiAzLjIxbC0yLjMuNzVzLTMuMjItMi4wOC00LjEzLTEuMjdjLS44Ni4zNyAxLjEgMy4yOCAxLjg4IDMuOTgtLjggMS4xNS04LjU1IDEyLjExLTguOTcgMTUuNjktLjI2IDIuMjcgMi4wMyAzLjUxIDMuNzIgNC4xMmExMS45MSAxMS45MSAwIDAgMCAxLjc0LjQ3YzEuNDItLjI2IDMuMzQtMi4wNCA1LjQ1LTMuMjR6IiBmaWx0ZXI9InVybCgjYikiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiNlNmU2ZTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjEiIGQ9Ik0yMy45NCAyOC4wOXM0LjQzLTEuODcgNC4yMi01Ljg0Ii8+PHBhdGggc3Ryb2tlPSIjZTZlNmU2IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMS40IiBkPSJNMTkuMSAxOC40N3MuNi0xLjg0IDMuNDYtMi4zIi8+PGVsbGlwc2UgY3g9IjIxLjAzIiBjeT0iMTgiIGZpbGw9IiNlNmU2ZTYiIHBhaW50LW9yZGVyPSJtYXJrZXJzIGZpbGwgc3Ryb2tlIiByeD0iMS4yNCIgcnk9IjEuMTciLz48cGF0aCBmaWxsPSIjZmZmIiBzdHJva2U9IiNlNmU2ZTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjQiIGQ9Ik05LjE3IDI5LjI0cy4yNS0uNjguOTItMS4xMiIvPjxwYXRoIGZpbGw9IiNmZmYiIHN0cm9rZT0iI2U2ZTZlNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjEuMiIgZD0iTTExLjY0IDMyLjI4Yy42OS0uODggMS41OC0xLjMyIDIuMzgtMS45NSIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2U2ZTZlNiIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjQiIGQ9Ik0zMC44IDE0Ljg3YzQuMzEgMi42NCA4LjQ3IDkuMjUgOC4xMiAyNi4wOCIvPjwvc3ZnPg==')} +.is2d .bishop.black {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGZpbHRlciBpZD0iYyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249IjAuMDEgMC4wMSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+PGZlRmxvb2QgZmxvb2QtY29sb3I9IiMwMDAiIGZsb29kLW9wYWNpdHk9Ii41IiByZXN1bHQ9ImZsb29kIi8+PGZlQ29tcG9zaXRlIGluPSJmbG9vZCIgaW4yPSJTb3VyY2VHcmFwaGljIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iY29tcG9zaXRlMSIvPjxmZUdhdXNzaWFuQmx1ciBpbj0iY29tcG9zaXRlMSIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249Ii4zIi8+PGZlT2Zmc2V0IGR4PSIxIiBkeT0iMSIgcmVzdWx0PSJvZmZzZXQiLz48ZmVDb21wb3NpdGUgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0ib2Zmc2V0IiByZXN1bHQ9ImNvbXBvc2l0ZTIiLz48L2ZpbHRlcj48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSIxMzE5NyIgeDI9IjEzMzQxIiB5MT0iLTk1OTEuMSIgeTI9Ii05NTkxLjEiIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTM0ODUuNyAyNTYyLjYpIHNjYWxlKC4yNjQ1OCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzYzNjM2MiLz48c3RvcCBvZmZzZXQ9IjEiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cGF0aCBmaWxsPSJ1cmwoI2EpIiBzdHJva2U9IiNlNmU2ZTYiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMS4xIiBkPSJNMjUgNi41NWMtLjg4IDAtMS42NS4yOS0yLjI2LjlhMi44NiAyLjg2IDAgMCAwLS45NSAyLjE2YzAgMS4yMy41OCAyLjEzIDEuNzYgMi43NC0yLjk3IDMuMjktOC43IDUuODItOC44MSAxMC44MyAwIDIuNjcgMS40NiA0Ljc2IDMuMyA2LjhsLTEuMSA1LjgzYzEuNjkuNTQgMy4wOC45NCA0LjgyIDEuMTMtMy44OCA0LjU4LTEwLjc5LTEuNzQtMTUuMjEgMi45M2wyLjMzIDMuNThjNS42LTMuOTYgMTMuMzggMy42NyAxNi4xMi0zLjk2IDIuNzUgNy42MyAxMC41MyAwIDE2LjEyIDMuOTZsMi4zMy0zLjU4Yy00LjQyLTQuNjctMTEuMzMgMS42NS0xNS4yLTIuOTNhMjMuMSAyMy4xIDAgMCAwIDQuODItMS4xM2wtMS4xMi01LjgzYzEuODUtMi4wNCAzLjMtNC4xMyAzLjMxLTYuOC0uMS01LTUuODQtNy41NC04LjgtMTAuODMgMS4xNy0uNjEgMS43NS0xLjUxIDEuNzUtMi43NCAwLS44NC0uMy0xLjU1LS45NS0yLjE2YTMuMSAzLjEgMCAwIDAtMi4yNi0uOXoiIGZpbHRlcj0idXJsKCNiKSIvPjxlbGxpcHNlIGN4PSIyNzIwLjMiIGN5PSItMjcxLjQiIGZpbGw9IiNlNmU2ZTYiIGNsYXNzPSJzdDE1IiBmaWx0ZXI9InVybCgjYykiIHJ4PSIxNi4zIiByeT0iMi41IiB0cmFuc2Zvcm09Im1hdHJpeCguMzMyMzIgMCAwIC4yNDk5OCAtODc5LjAxIDEwMi40NykiLz48ZWxsaXBzZSBjeD0iMjUiIGN5PSI5LjYxIiBmaWxsPSIjZTZlNmU2IiBjbGFzcz0ic3QxNSIgcng9IjEuMTQiIHJ5PSIxLjE1Ii8+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTZlNmU2IiBzdHJva2Utd2lkdGg9IjEuNCIgZD0iTTIxLjMzIDIzLjI3aDcuMzRNMjUgMTkuOTN2Ni43NSIvPjwvc3ZnPg==')} +.is2d .rook.black {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iNDUwMS41IiB4Mj0iNDU5NC42IiB5MT0iLTU3Mi40IiB5Mj0iLTU3Mi40IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4zNDIwOCAwIDAgLjI4MzcgLTE1MzAuOCAxODcuMzkpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM2MzYzNjIi8+PHN0b3Agb2Zmc2V0PSIxIi8+PC9saW5lYXJHcmFkaWVudD48ZmlsdGVyIGlkPSJiIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiPjxmZUZsb29kIGZsb29kLWNvbG9yPSIjMDAwIiBmbG9vZC1vcGFjaXR5PSIuNSIgcmVzdWx0PSJmbG9vZCIvPjxmZUNvbXBvc2l0ZSBpbj0iZmxvb2QiIGluMj0iU291cmNlR3JhcGhpYyIgb3BlcmF0b3I9ImluIiByZXN1bHQ9ImNvbXBvc2l0ZTEiLz48ZmVHYXVzc2lhbkJsdXIgaW49ImNvbXBvc2l0ZTEiIHJlc3VsdD0iYmx1ciIgc3RkRGV2aWF0aW9uPSIuMyIvPjxmZU9mZnNldCBkeD0iMSIgZHk9IjEiIHJlc3VsdD0ib2Zmc2V0Ii8+PGZlQ29tcG9zaXRlIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9Im9mZnNldCIgcmVzdWx0PSJjb21wb3NpdGUyIi8+PC9maWx0ZXI+PC9kZWZzPjxwYXRoIGZpbGw9InVybCgjYSkiIHN0cm9rZT0iI2U2ZTZlNiIgc3Ryb2tlLXdpZHRoPSIxLjE0IiBkPSJNMjEuOTMgNi41NXYyLjkzaC00LjA5VjYuODloLTUuOHY3Ljk4TDE2LjU4IDE4djEyLjU3bC0zLjg1IDIuNDh2NS4yMUg5LjY2djUuMThoMzAuNjh2LTUuMThoLTMuMDd2LTUuMmwtMy44NS0yLjVWMTguMDVsNC41My0zLjJWNi44OGgtNS44djIuNTloLTQuNDJWNi41NWgtMi45eiIgY2xhc3M9InN0MTQiIGZpbHRlcj0idXJsKCNiKSIgdHJhbnNmb3JtPSJtYXRyaXgoMS4wMDU1IDAgMCAuOTE5OCAtLjIgMy41KSIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2U2ZTZlNiIgc3Ryb2tlLXdpZHRoPSIxLjQiIGQ9Ik0xOC44IDMxLjRoMTJNMTguOCAyMGgxMiIvPjwvc3ZnPg==')} +.is2d .queen.black {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGZpbHRlciBpZD0iYyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249IjAuMDEgMC4wMSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+PGZlRmxvb2QgZmxvb2QtY29sb3I9IiMwMDAiIGZsb29kLW9wYWNpdHk9Ii40OTgiIHJlc3VsdD0iZmxvb2QiLz48ZmVDb21wb3NpdGUgaW49ImZsb29kIiBpbjI9IlNvdXJjZUdyYXBoaWMiIG9wZXJhdG9yPSJpbiIgcmVzdWx0PSJjb21wb3NpdGUxIi8+PGZlR2F1c3NpYW5CbHVyIGluPSJjb21wb3NpdGUxIiByZXN1bHQ9ImJsdXIiIHN0ZERldmlhdGlvbj0iLjMiLz48ZmVPZmZzZXQgZHg9IjEiIGR5PSIxIiByZXN1bHQ9Im9mZnNldCIvPjxmZUNvbXBvc2l0ZSBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJvZmZzZXQiIHJlc3VsdD0iY29tcG9zaXRlMiIvPjwvZmlsdGVyPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeDE9Ii03MS42MzciIHgyPSItMzAuNjc4IiB5MT0iLTgzLjMyNSIgeTI9Ii04My4zMjUiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLjk3NjQ0IDAgMCAuOTkyODYgNzQuOTUyIDEwNy43MykiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzYzNjM2MiLz48c3RvcCBvZmZzZXQ9IjEiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cGF0aCBmaWxsPSJ1cmwoI2EpIiBzdHJva2U9IiNlNmU2ZTYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIxLjEiIGQ9Ik0yNC45OTQgNi41NDl2MGMtMS41NjguMDA2LTIuODM1IDEuMTgxLTIuODM2IDIuNjMuMDAyIDEuMTkzLjg3MyAyLjIzNSAyLjEyMiAyLjUzOS0uNjg4IDQuNDUtMS45NjcgOS43MjYtMi42MzQgMTQuMTEybC00LjA3LTEyLjkyN2MuOTY4LS40NDQgMS41OC0xLjM1NiAxLjU4LTIuMzUzIDAtMS40NTMtMS4yNzUtMi42My0yLjg0Ny0yLjYzcy0yLjg0NyAxLjE3Ny0yLjg0NyAyLjYzYy4wMDIgMS4yMDUuODkgMi4yNTUgMi4xNTcgMi41NDdsLS40NCAxMy4yNTgtNS40ODItMTAuNjExYy45NTEtLjQ1IDEuNTUtMS4zNTQgMS41NS0yLjM0IDAtMS40NTMtMS4yNzUtMi42My0yLjg0Ny0yLjYzLTEuNTczIDAtMi44NDcgMS4xNzctMi44NDcgMi42MyAwIDEuMzM0IDEuMDg0IDIuNDU2IDIuNTE5IDIuNjFsMi43NiAxNi41MDcgNC4wNSA1LjI1OC0xLjAwNCAzLjYzNGMtLjA0Mi42NTYgNC44NDggMi4wMjggMTEuMTIyIDIuMDQgNi4yNzMtLjAxMiAxMS4xNjQtMS4zODQgMTEuMTIyLTIuMDRsLTEuMDA1LTMuNjM0IDQuMDUtNS4yNTggMi43Ni0xNi41MDdjMS40MzUtLjE1NCAyLjUxOS0xLjI3NiAyLjUyLTIuNjEgMC0xLjQ1My0xLjI3NS0yLjYzLTIuODQ3LTIuNjMtMS41NzMgMC0yLjg0NyAxLjE3Ny0yLjg0NyAyLjYzIDAgLjk4Ni41OTggMS44OSAxLjU1IDIuMzRsLTUuNDg0IDEwLjYxLS40MzktMTMuMjU3YzEuMjY2LS4yOTIgMi4xNTUtMS4zNDIgMi4xNTctMi41NDcgMC0xLjQ1My0xLjI3NS0yLjYzLTIuODQ3LTIuNjNzLTIuODQ3IDEuMTc3LTIuODQ3IDIuNjNjMCAuOTk3LjYxMiAxLjkwOSAxLjU4IDIuMzUzbC00LjA3IDEyLjkyN2MtLjY2Ny00LjM4Ni0xLjk0Ni05LjY2Mi0yLjYzNC0xNC4xMTIgMS4yNDktLjMwNCAyLjEyLTEuMzQ2IDIuMTIyLTIuNTQgMC0xLjQ0OC0xLjI2OC0yLjYyMy0yLjgzNi0yLjYyOXYwaC0uMDExeiIgZmlsdGVyPSJ1cmwoI2IpIi8+PGVsbGlwc2UgY3g9IjQ3MDguNyIgY3k9Ii0yNTE3LjYiIGZpbGw9IiNlNmU2ZTYiIGNsYXNzPSJzdDE1IiBmaWx0ZXI9InVybCgjYykiIHJ4PSIzMi4xMjYiIHJ5PSIyLjg0NCIgdHJhbnNmb3JtPSJtYXRyaXgoLjI1OTM5IDAgMCAuMjkyOTggLTExOTYuNCA3NzguMTIpIi8+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTZlNmU2IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik0xNS4xNzIgMzQuMDc2czIuNy0xLjI0OSA5LjgwMi0xLjI1NmM3LjEwMy0uMDEgOS44MDEgMS4yNTYgOS44MDEgMS4yNTYiIHBhaW50LW9yZGVyPSJzdHJva2UgZmlsbCBtYXJrZXJzIi8+PC9zdmc+')} +.is2d .king.black {background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgc2hhcGUtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHRleHQtcmVuZGVyaW5nPSJnZW9tZXRyaWNQcmVjaXNpb24iIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PGZpbHRlciBpZD0iYyIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249IjAuMDEgMC4wMSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImIiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+PGZlRmxvb2QgZmxvb2QtY29sb3I9IiMwMDAiIGZsb29kLW9wYWNpdHk9Ii41IiByZXN1bHQ9ImZsb29kIi8+PGZlQ29tcG9zaXRlIGluPSJmbG9vZCIgaW4yPSJTb3VyY2VHcmFwaGljIiBvcGVyYXRvcj0iaW4iIHJlc3VsdD0iY29tcG9zaXRlMSIvPjxmZUdhdXNzaWFuQmx1ciBpbj0iY29tcG9zaXRlMSIgcmVzdWx0PSJibHVyIiBzdGREZXZpYXRpb249Ii42Ii8+PGZlT2Zmc2V0IGR4PSIxLjYiIGR5PSIxLjQiIHJlc3VsdD0ib2Zmc2V0Ii8+PGZlQ29tcG9zaXRlIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9Im9mZnNldCIgcmVzdWx0PSJjb21wb3NpdGUyIi8+PC9maWx0ZXI+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iMjk4Ni40IiB4Mj0iMzEyOC40IiB5MT0iMTYyMy44IiB5Mj0iMTYyMy44IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KC4yNzE0MSAwIDAgLjI3MjE4IC04MDQuODEgLTQxNy40NSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzYzNjM2MiLz48c3RvcCBvZmZzZXQ9IjEiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cGF0aCBmaWxsPSJ1cmwoI2EpIiBzdHJva2U9IiNlNmU2ZTYiIHN0cm9rZS13aWR0aD0iMS4xIiBkPSJNMjMuMjggNS41NXYzLjI0aC0zLjM2djIuOTJoMy4zNnYxLjc2Yy0zLjM2IDIuMTItMyA1Ljc0LTMgNS43NC0xMS04LjUyLTIwLjY3IDcuNTYtNy45NCAxMy4wNXY4LjczYzAgLjk1IDUuNjcgMi40NiAxMi42NiAyLjQ2czEyLjY2LTEuNSAxMi42Ni0yLjQ2di04LjczYzEyLjcyLTUuNDkgMy4wNi0yMS41Ny03Ljk1LTEzLjA1IDAgMCAuMzgtMy42Mi0zLTUuNzRWMTEuN2gzLjM3VjguNzloLTMuMzZWNS41NUgyNXoiIGZpbHRlcj0idXJsKCNiKSIvPjxlbGxpcHNlIGN4PSI3MS4wOCIgY3k9IjEzMS41NCIgZmlsbD0iI2U2ZTZlNiIgY2xhc3M9InN0MTUiIGZpbHRlcj0idXJsKCNjKSIgcng9IjMyLjEzIiByeT0iMi44NCIgdHJhbnNmb3JtPSJtYXRyaXgoLjI4NTMzIDAgMCAuMzIyMyA0LjcyIC0xLjk4KSIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2U2ZTZlNiIgc3Ryb2tlLXdpZHRoPSIxLjQiIGQ9Ik0yNy4wMyAzMC4yN2MxLjUtMTIuMSAxMS45NC0xMi40NCAxMy4zNy03LjM4IDEuNDIgNS4wNi00Ljc0IDcuMzgtNC43NCA3LjM4cy00Ljg3LS42NC0xMC42Ni0uNjQtMTAuNjYuNjQtMTAuNjYuNjQtNi4xNi0yLjMyLTQuNzMtNy4zOCAxMS44Ny00LjczIDEzLjM2IDcuMzgiLz48L3N2Zz4=')} diff --git a/public/piece/cardinal/bB.svg b/public/piece/cardinal/bB.svg index 010eb5e91f0ed..7050e902f4d14 100644 --- a/public/piece/cardinal/bB.svg +++ b/public/piece/cardinal/bB.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/piece/cardinal/bK.svg b/public/piece/cardinal/bK.svg index 17854b81c3270..2f5ff4ae96a3c 100644 --- a/public/piece/cardinal/bK.svg +++ b/public/piece/cardinal/bK.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/piece/cardinal/bN.svg b/public/piece/cardinal/bN.svg index a5de017abf128..57389102bf4bd 100644 --- a/public/piece/cardinal/bN.svg +++ b/public/piece/cardinal/bN.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/piece/cardinal/bP.svg b/public/piece/cardinal/bP.svg index f080eace9d18d..3a37a7da284cf 100644 --- a/public/piece/cardinal/bP.svg +++ b/public/piece/cardinal/bP.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/piece/cardinal/bQ.svg b/public/piece/cardinal/bQ.svg index 5f9eccfe8938a..204d14991cbeb 100644 --- a/public/piece/cardinal/bQ.svg +++ b/public/piece/cardinal/bQ.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/piece/cardinal/bR.svg b/public/piece/cardinal/bR.svg index d9f89d553d95a..a01b04d2ab4fb 100644 --- a/public/piece/cardinal/bR.svg +++ b/public/piece/cardinal/bR.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/piece/cardinal/wB.svg b/public/piece/cardinal/wB.svg index 2d7b358b1eede..11450c687d28d 100644 --- a/public/piece/cardinal/wB.svg +++ b/public/piece/cardinal/wB.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/piece/cardinal/wK.svg b/public/piece/cardinal/wK.svg index e9e076f8e9e95..58ec38d0a67b1 100644 --- a/public/piece/cardinal/wK.svg +++ b/public/piece/cardinal/wK.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/piece/cardinal/wN.svg b/public/piece/cardinal/wN.svg index b596a4c23a161..23944a3082635 100644 --- a/public/piece/cardinal/wN.svg +++ b/public/piece/cardinal/wN.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/piece/cardinal/wP.svg b/public/piece/cardinal/wP.svg index 86287ed6c69e3..3b47f67a5abfe 100644 --- a/public/piece/cardinal/wP.svg +++ b/public/piece/cardinal/wP.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/piece/cardinal/wQ.svg b/public/piece/cardinal/wQ.svg index c561ed108b80e..2e904c745db9e 100644 --- a/public/piece/cardinal/wQ.svg +++ b/public/piece/cardinal/wQ.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/piece/cardinal/wR.svg b/public/piece/cardinal/wR.svg index 548ad4e659937..8ba39b2b3d1bd 100644 --- a/public/piece/cardinal/wR.svg +++ b/public/piece/cardinal/wR.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/translation/dest/activity/es-ES.xml b/translation/dest/activity/es-ES.xml index a5296ee69bd19..4e71a086008e0 100644 --- a/translation/dest/activity/es-ES.xml +++ b/translation/dest/activity/es-ES.xml @@ -66,7 +66,7 @@ Ha competido en %s torneo suizo Ha competido en %s torneos suizos - #%1$s en la clasificación en %2$s + #%1$s En la Clasificatoria de %2$s Registrado en Lichess Miembro de %s equipo diff --git a/translation/dest/activity/he-IL.xml b/translation/dest/activity/he-IL.xml index fa46e214bcc9e..622f988d615b1 100644 --- a/translation/dest/activity/he-IL.xml +++ b/translation/dest/activity/he-IL.xml @@ -82,23 +82,23 @@ השתתף/ה בטורניר זירה %s - השתתף/ה ב-%s טורנירי זירה - השתתף/ה ב-%s טורנירי זירה - השתתף/ה ב-%s טורנירי זירה + השתתף/ה ב־%s טורנירי זירה + השתתף/ה ב־%s טורנירי זירה + השתתף/ה ב־%s טורנירי זירה - סיים/ה במקום #%1$s (אחוזון %%%2$s) עם משחק %3$s ב-%4$s + סיים/ה במקום #%1$s (אחוזון %%%2$s) עם משחק %3$s ב־%4$s סיים/ה במקום #%1$s (אחוזון %%%2$s) עם %3$s משחקים ב%4$s סיים/ה במקום #%1$s (אחוזון %%%2$s) עם %3$s משחקים ב%4$s סיים/ה במקום #%1$s (אחוזון %%%2$s) עם %3$s משחקים ב%4$s השתתף/ה בטורניר שוויצרי %s - השתתף/ה ב-%s טורנירים שוויצריים - השתתף/ה ב-%s טורנירים שוויצריים - השתתף/ה ב-%s טורנירים שוויצריים + השתתף/ה ב־%s טורנירים שוויצריים + השתתף/ה ב־%s טורנירים שוויצריים + השתתף/ה ב־%s טורנירים שוויצריים - סיים/ה במקום %1$s ב-%2$s + סיים/ה במקום %1$s ב־%2$s נרשם/ה לlichess.org הצטרף/ה לקבוצה %s diff --git a/translation/dest/appeal/ca-ES.xml b/translation/dest/appeal/ca-ES.xml index e16728297af9f..8b702a604fcb1 100644 --- a/translation/dest/appeal/ca-ES.xml +++ b/translation/dest/appeal/ca-ES.xml @@ -7,15 +7,15 @@ El teu compte té prohibit jugar tornejos amb premis reals. El teu compte està marcat com a manipulació de la puntuació. Ho definim com manipular la puntuació perdent partides expressament, o jugant contra un altre compte que està perdent partides deliberadament. - El teu compte està silenciat. - Llegiu la nostra %s. No respectar les normes de comunicació pot provocar que els comptes se silenciïn. + El teu compte està silenciat. + Llegiu la nostra %s. No respectar les normes de comunicació pot provocar que els comptes se silenciïn. El vostre compte ha estat exclòs de les classificacions. Ho definim com fer servir alguna forma bruta per pujar a la clarificació. El vostre compte ha estat tancat pels moderadors. Les vostres entrades del blog han estat amagades pels moderadors. Assegureu-vos de llegir de nou la nostra %s. - Teniu un temps d\'espera per jugar. - guia de comunicació + Teniu un temps d\'espera per jugar. + guia de comunicació normes del blog Joc net diff --git a/translation/dest/appeal/da-DK.xml b/translation/dest/appeal/da-DK.xml index 8694ae3aa9e5e..d01a13e1b86e9 100644 --- a/translation/dest/appeal/da-DK.xml +++ b/translation/dest/appeal/da-DK.xml @@ -7,15 +7,15 @@ Din konto er udelukket fra turneringer med rigtige præmier. Din konto er markeret for ratingmanipulation. Vi definerer dette som bevidst manipulation af rating ved at tabe partier med vilje eller ved at spille mod en anden konto, der bevidst taber partier. - Din konto er gjort stum. - Læs vores %s. Hvis retningslinjerne for kommunikation ikke følges, kan det resultere i, at konti bliver gjort stumme. + Din konto er gjort stum. + Læs vores %s. Hvis retningslinjerne for kommunikation ikke følges, kan det resultere i, at konti bliver gjort stumme. Din konto er blevet udelukket fra ranglister. Vi definerer dette som at bruge unfair metoder til at komme på ranglisten. Din konto blev lukket af moderatorer. Dine blogs er blevet skjult af moderatorer. Sørg for igen at læse vores %s. - Du har en spille-timeout. - retningslinjer for kommunikation + Du har en spille-timeout. + retningslinjer for kommunikation blogregler Fairplay diff --git a/translation/dest/appeal/en-US.xml b/translation/dest/appeal/en-US.xml index f85d0c50813f1..6e07d2cd1155d 100644 --- a/translation/dest/appeal/en-US.xml +++ b/translation/dest/appeal/en-US.xml @@ -7,15 +7,15 @@ Your account is banned from tournaments with real prizes. Your account is marked for rating manipulation. We define this as deliberately manipulating a rating by losing games on purpose or by playing against another account that is deliberately losing games. - Your account is muted. - Read our %s. Failure to comply with the communication guidelines may result in your account being muted. + Your account is muted. + Read our %s. Failure to comply with the communication guidelines may result in your account being muted. Your account has been excluded from leaderboards. We define this as using any unfair way to get on the leaderboard. Your account was closed by moderators. Your blogs have been hidden by moderators. Make sure to read again our %s. - You have a play timeout. - communication guidelines + You have a play timeout. + communication guidelines blog rules Fair Play diff --git a/translation/dest/appeal/es-ES.xml b/translation/dest/appeal/es-ES.xml index 95dfda7f61b58..7c5e0344ab51a 100644 --- a/translation/dest/appeal/es-ES.xml +++ b/translation/dest/appeal/es-ES.xml @@ -7,15 +7,15 @@ Tu cuenta tiene prohibido participar en torneos con premios reales. Tu cuenta está marcada por manipulación de la calificación. Definimos manipular deliberadamente la calificación como el perder partidas a propósito, o jugar contra otra cuenta que está perdiendo partidas deliberadamente. - Tu cuenta está silenciada. - Lee nuestro %s. No seguir las directrices de comunicación puede provocar que las cuentas sean silenciadas. + Tu cuenta está silenciada. + Lee nuestro %s. No seguir las directrices de comunicación puede provocar que las cuentas sean silenciadas. Se ha excluido a su cuenta de las tablas de clasificación. Definimos esto como usar cualquier forma injusta de posicionarse en la tabla de clasificaciones. Tu cuenta fue cerrada por moderadores. Tus blogs han sido ocultados por los moderadores. Asegúrate de leer de nuevo nuestro %s. - Tienes un tiempo de espera agotado. - directrices de comunicación + Tienes un tiempo de espera agotado. + directrices de comunicación reglas del blog Juego limpio diff --git a/translation/dest/appeal/fi-FI.xml b/translation/dest/appeal/fi-FI.xml index 5cd863947840f..b5428565d6766 100644 --- a/translation/dest/appeal/fi-FI.xml +++ b/translation/dest/appeal/fi-FI.xml @@ -7,15 +7,15 @@ Tunnuksesi on estetty pelaamasta turnauksissa, joissa on oikeita palkintoja. Tunnukseesi on lisätty merkintä vahvuusluvun manipuloinnista. Määritelmämme mukaan tällä tarkoitetaan vahvuusluvun peukalointia joko häviämällä pelejä tahallisesti tai pelaamalla sellaista tunnusta vastaan, joka häviää pelejä tarkoituksella. - Tunnuksesi on mykistetty. - Lue meidän %s. Viestintäsääntöjen noudattamatta jättäminen voi johtaa tunnusten mykistämiseen. + Tunnuksesi on mykistetty. + Lue meidän %s. Viestintäsääntöjen noudattamatta jättäminen voi johtaa tunnusten mykistämiseen. Tunnuksesi on estetty näkymästä pistetaulukoissa. Määritelmämme mukaan tämä tarkoittaa mitä tahansa epäreilua keinoa, jolla tavoitellaan pistetaulukkoon pääsyä. Moderaattorit ovat sulkeneet tunnuksesi. Moderaattorit ovat piilottaneet blogisi. Ole hyvä ja lue uudelleen %s. - Sinulle on asetettu pelitauko. - viestintäsääntömme + Sinulle on asetettu pelitauko. + viestintäsääntömme blogisäännöt Reilu peli diff --git a/translation/dest/appeal/fr-FR.xml b/translation/dest/appeal/fr-FR.xml index 2eac3d7a7eacb..02c1ff3018a3c 100644 --- a/translation/dest/appeal/fr-FR.xml +++ b/translation/dest/appeal/fr-FR.xml @@ -8,14 +8,14 @@ Votre compte est marqué pour manipulation de classement (cote). Par manipulation de classement, on entend une manipulation délibérée du classement par un joueur qui perd volontairement des parties ou qui affronte un autre joueur en sachant que ce dernier perd volontairement ses parties. Vous ne pouvez plus clavarder avec votre adversaire ni envoyer des messages. - Lisez nos %s. Ne pas les suivre peut entraîner la mise en sourdine de votre compte. + Lisez nos %s. Ne pas les suivre peut entraîner la mise en sourdine de votre compte. Vous avez été exclu(e) du classement. L\'exclusion du classement résulte habituellement de l\'utilisation délibérée d\'un moyen déloyal pour faire partie du classement. Votre compte a été fermé par les modérateurs. Votre blogue a été masqué par les modérateurs. Relisez nos %s. Vous ne pouvez plus jouer de partie temporairement. - Lignes directrices de communication + Lignes directrices de communication Règles relatives aux blogues Esprit sportif diff --git a/translation/dest/appeal/gl-ES.xml b/translation/dest/appeal/gl-ES.xml index 0add6635f5c5a..48412370e5cda 100644 --- a/translation/dest/appeal/gl-ES.xml +++ b/translation/dest/appeal/gl-ES.xml @@ -7,15 +7,15 @@ A túa conta ten prohibido participar en torneos con premios reais. A túa conta está marcada por manipulación da puntuación. Isto definímolo como a manipulación deliberada da puntuación mediante a derrota adrede das partidas, ou mediante o xogo contra outra conta que perde deliberadamente as partidas. - A túa conta está silenciada. - Le as nosas %s. Non respectar as directrices de comunicación pode implicar que as contas sexan silenciadas. + A túa conta está silenciada. + Le as nosas %s. Non respectar as directrices de comunicación pode implicar que as contas sexan silenciadas. A túa conta foi excluída das listaxes de líderes. Isto definímolo como o uso de calquera procedemento inxusto para aparecer nas listaxes de líderes. A túa conta foi pechada polos moderadores. As túas publicacións no blog foron agochadas polos moderadores. Asegúrate de ler de novo as nosas %s. - Non podes xogar debido a unha suspensión temporal. - directrices de comunicación + Non podes xogar debido a unha suspensión temporal. + directrices de comunicación regras do blog Xogo limpo diff --git a/translation/dest/appeal/gsw-CH.xml b/translation/dest/appeal/gsw-CH.xml index 3699abb8c5a92..1ba63439b4a23 100644 --- a/translation/dest/appeal/gsw-CH.xml +++ b/translation/dest/appeal/gsw-CH.xml @@ -7,15 +7,15 @@ Dis Konto isch gschperrt, für Turnier mit ächte Prise. Dis Konto isch markiert wäge Wertigs - Manipulation. Mir definiered das als absichtlichi Manipulation vu de Wertig, dur absichtlichs Verlüre vu Schpil oder dur Schpille gäge es anders Konto, wo ansichtlich Schpil verlürt. - Dis Konto isch schtumm g\'schaltet. - Lies euseri %s. D\'Nödihaltig vu de Kommunikationsrichtlinie chann dezue fühere, dass Konte schtumm g\'eschaltet werded. + Dis Konto isch schtumm g\'schaltet. + Lies euseri %s. D\'Nödihaltig vu de Kommunikationsrichtlinie chann dezue fühere, dass Konte schtumm g\'eschaltet werded. Dis Konto isch vu de Beschtelischte usgschlosse. Mir definiered das als es Usnütze vu jegliche unfaire Methode, zum uf d\'Beschtelischte zcho. Dis Konto isch vu de Moderatore g\'schlosse worde. Dini Tagebüecher (Blogs) händ d\'Moderatore unsichtbar g\'macht. Lies unbedingt nomal euseri %s. - Du häsch es Schpil - Timeout. - Kommunikations - Richtlinie + Du häsch es Schpil - Timeout. + Kommunikations - Richtlinie Tagebuech Regle Fair Play diff --git a/translation/dest/appeal/he-IL.xml b/translation/dest/appeal/he-IL.xml index 623fe47c65075..0222635f83f7c 100644 --- a/translation/dest/appeal/he-IL.xml +++ b/translation/dest/appeal/he-IL.xml @@ -7,15 +7,15 @@ חשבונך לא יכול לשמש להצטרפות לטורנירים עם פרסים אמיתיים. חשבונך מוגבל בשל מניפולציה בדירוג. אסור לבצע מניפולציה בדירוג באמצעות הפסד של משחקים בכוונה או שימוש בחשבון אחר כדי להקטין את דירוגך. - חשבונך מושתק. - קראו את %s שלנו. חובה לעמוד בכללי השיח שלנו כדי להימנע מהשתקה של החשבון. + חשבונך מושתק. + קראו את %s שלנו. חובה לעמוד בכללי השיח שלנו כדי להימנע מהשתקה של החשבון. חשבונך לא יכול להופיע בטבלאות האלופים. חשבונות שמשתמשים בדרכים לא כשרות כדי להופיע בטבלאות יימחקו מהן. חשבונך נסגר על ידי המנהלים. הפוסטים שלך בבלוג הוסתרו על ידי המנהלים. אנא קראו את %s שלנו שוב. - חשבונך מושעה ממשחקים. - כללי השיח + חשבונך מושעה ממשחקים. + כללי השיח חוקי הבלוג משחק ההוגן diff --git a/translation/dest/appeal/ko-KR.xml b/translation/dest/appeal/ko-KR.xml index 7269ec3ebcf3d..6c608ec7c716e 100644 --- a/translation/dest/appeal/ko-KR.xml +++ b/translation/dest/appeal/ko-KR.xml @@ -7,15 +7,15 @@ 당신의 계정은 현금 상금이 걸려 있는 대회 참가가 금지되어 있습니다. 당신의 계정은 레이팅 조작으로 표시되었습니다. 이것은 고의로 게임에서 패배하거나, 고의로 게임에서 패배를 하고 있는 계정과 플레이함으로써 의도적으로 레이팅을 조작하는 행위로 정의됩니다. - 당신의 계정에 침묵 제재가 적용중입니다. - %s을 읽어보세요. 의사소통 가이드라인을 준수하지 않는다면, 계정에 침묵 제재가 적용될 수 있습니다. + 당신의 계정에 침묵 제재가 적용중입니다. + %s을 읽어보세요. 의사소통 가이드라인을 준수하지 않는다면, 계정에 침묵 제재가 적용될 수 있습니다. 당신의 계정은 순위표에서 제외되었습니다. 이것은 순위표에 오르기 위해서 임의의 부정직한 방법을 사용하는 것으로 정의됩니다. 당신의 계정은 운영진에 의해 폐쇄되었습니다. 당신의 블로그는 운영진에 의해 숨김 처리 되었습니다. 꼭 %s를 읽어주세요. - 당신은 게임 타임아웃 상태입니다. - 의사소통 가이드라인 + 당신은 게임 타임아웃 상태입니다. + 의사소통 가이드라인 블로그 규칙 페어플레이 diff --git a/translation/dest/appeal/nb-NO.xml b/translation/dest/appeal/nb-NO.xml index 422b2a65069c5..d83d78c849132 100644 --- a/translation/dest/appeal/nb-NO.xml +++ b/translation/dest/appeal/nb-NO.xml @@ -8,14 +8,14 @@ Kontoen din har en anmerkning for manipulering av rating. Dette defineres som bevisst manipulering av rating ved å tape partier med vilje eller ved å spille mot en annen konto som taper partier med vilje. Kontoen din er gjort stum. - Les våre %s. Hvis retningslinjene ikke blir fulgt, kan det føre til at kontoer blir gjort stumme. + Les våre %s. Hvis retningslinjene ikke blir fulgt, kan det føre til at kontoer blir gjort stumme. Kontoen din er utelatt fra ledertabellene. Dette defineres som å bruke urettferdige metoder for å komme på ledertabellene. Kontoen din ble avsluttet av moderatorene. Bloggene dine er blitt skjult av moderatorene. Les våre %s. Du har fått timeout fra å spille. - retningslinjer for kommunikasjon + retningslinjer for kommunikasjon bloggregler sportslig opptreden diff --git a/translation/dest/appeal/nn-NO.xml b/translation/dest/appeal/nn-NO.xml index 556bc5de8a051..b4cd4ab4ecbab 100644 --- a/translation/dest/appeal/nn-NO.xml +++ b/translation/dest/appeal/nn-NO.xml @@ -7,15 +7,15 @@ Kontoen din er stengd ute frå deltaking i turneringar med premiepengar. Kontoen din er merka for ratingmanipulering. Vi definerer dette som bevisst manipulering av rating ved med hensikt å tapa parti, eller ved å spela mot ein annan konto som med hensikt tapar parti. - Kontoen din er gjort stum. - Les vår %s. Dersom desse retningslinjene ikkje vert følgd kan det føre til at kontoane vert gjort stumme. + Kontoen din er gjort stum. + Les vår %s. Dersom desse retningslinjene ikkje vert følgd kan det føre til at kontoane vert gjort stumme. Kontoen din er utelukka frå ranglistene. Vi definerer dette som å bruka urettvise måtar å koma på rangeringslistene. Moderatorane har stengd kontoen din. Moderatorane har skjult bloggane dine. Sørg for å lese vår %s opp att. - Du har ei pause i spelet. - retningslinjer for kommunikasjon + Du har ei pause i spelet. + retningslinjer for kommunikasjon bloggreglar Fair Play diff --git a/translation/dest/appeal/pt-BR.xml b/translation/dest/appeal/pt-BR.xml index 9bf10bccc6475..59f4a5c176ff7 100644 --- a/translation/dest/appeal/pt-BR.xml +++ b/translation/dest/appeal/pt-BR.xml @@ -8,14 +8,14 @@ Sua conta está marcada por manipulação de rating. Nós definimos isso como uma manipulação deliberada perdendo jogos de propósito ou jogando contra outra conta que está deliberadamente perdendo jogos. Sua conta está silenciada. - Leia as nossas %s. O não cumprimento das diretrizes de comunicação pode resultar no silenciamento das contas. + Leia as nossas %s. O não cumprimento das diretrizes de comunicação pode resultar no silenciamento das contas. Sua conta foi excluída das tabelas de classificação. Definimos isto como uma forma injusta de conseguir posições na tabela de classificações. Sua conta foi fechada pelos moderadores. Seus blogs foram ocultados pelos moderadores. Certifique-se de ler novamente as nossas %s. Você está temporariamente suspenso de jogar. - diretrizes de comunicação + diretrizes de comunicação regras do blog Jogo Limpo diff --git a/translation/dest/appeal/pt-PT.xml b/translation/dest/appeal/pt-PT.xml index 5d8bd4c3879e4..0c243ffdd785c 100644 --- a/translation/dest/appeal/pt-PT.xml +++ b/translation/dest/appeal/pt-PT.xml @@ -7,15 +7,15 @@ A tua conta está banida dos torneios com prémios reais. A tua conta está marcada por manipulação de classificação. Definimos isto como manipular deliberadamente a classificação ao perder jogos de propósito ou ao jogar contra outra conta que esteja deliberadamente a perder jogos. - A tua conta foi silenciada. - Leia as nossas %s. O não cumprimento das diretrizes de comunicação pode resultar no silenciamento das contas. + A tua conta foi silenciada. + Leia as nossas %s. O não cumprimento das diretrizes de comunicação pode resultar no silenciamento das contas. A tua conta foi excluída das tabelas de classificação. Definimos isto como a utilização de qualquer forma injusta para entrar na tabela de liderança. A tua conta foi encerrada pelos moderadores. Os teus blogues foram ocultados pelos moderadores. Certifica que leias novamente as nossas %s. - Estás temporariamente impedido de jogar. - diretrizes de comunicação + Estás temporariamente impedido de jogar. + diretrizes de comunicação regras do blogue Jogo Justo diff --git a/translation/dest/appeal/ru-RU.xml b/translation/dest/appeal/ru-RU.xml index 3046326e13111..0a71961c4ff8a 100644 --- a/translation/dest/appeal/ru-RU.xml +++ b/translation/dest/appeal/ru-RU.xml @@ -7,15 +7,15 @@ Ваша учётная запись заблокирована для участия в турнирах с реальными призами. Ваша учётная запись помечена за манипуляции рейтингом. Мы определяем это как преднамеренное манипулирование рейтингом путём намеренного проигрыша партий или игрой против другого игрока, который намеренно проигрывает партии. - Ваша учётная запись ограничена. - Прочтите наши %s. Несоблюдение правил общения может привести к ограничению учётных записей. + Ваша учётная запись ограничена. + Прочтите наши %s. Несоблюдение правил общения может привести к ограничению учётных записей. Ваша учётная запись исключена из списка лидеров. Мы определяем это как использование нечестного способа попасть в список лидеров. Ваша учётная запись была закрыта модераторами. Ваши блоги были скрыты модераторами. Обязательно перечитайте наши %s. - У вас есть таймаут. - правила общения + У вас есть таймаут. + правила общения правила ведения блогов Честная игра diff --git a/translation/dest/appeal/sl-SI.xml b/translation/dest/appeal/sl-SI.xml index 46ad773473d8f..45eca1b4c2adf 100644 --- a/translation/dest/appeal/sl-SI.xml +++ b/translation/dest/appeal/sl-SI.xml @@ -7,15 +7,15 @@ Vaš račun je prepovedan za turnirje s pravimi nagradami. Vaš račun je označen za manipulacijo z rejtingi. To opredeljujemo kot namerno manipuliranje z rejtingom z namerno izgubo iger ali z igranjem proti drugemu računu, ki namerno izgublja igre. - Vaš račun je izklopljen. - Preberite naše %s. Neupoštevanje komunikacijskih smernic lahko povzroči utišanje računov. + Vaš račun je izklopljen. + Preberite naše %s. Neupoštevanje komunikacijskih smernic lahko povzroči utišanje računov. Vaš račun je bil izključen iz lestvic najboljših. Vaš račun je bil izključen iz lestvic najboljših. Vaš račun so zaprli moderatorji. Moderatorji so skrili vaše bloge. Ponovno preberite naše %s. - Imate časovno omejitev za igranje. - komunikacijske smernice + Imate časovno omejitev za igranje. + komunikacijske smernice pravila bloga Poštena igra diff --git a/translation/dest/appeal/sq-AL.xml b/translation/dest/appeal/sq-AL.xml index d4d6d4cedfc11..f908cbf8605d3 100644 --- a/translation/dest/appeal/sq-AL.xml +++ b/translation/dest/appeal/sq-AL.xml @@ -7,14 +7,14 @@ Llogarisë tuaj i është ndaluar të marrë pjesë në turne me çmime të njëmendta. Llogarisë tuaj i është vënë shenjë për manipulim vlerësimi. Këtë e përkufizojmë si manipulim me vetëdije i vlerësimit, duke humbur lojëra, ose duke luajtur kudnër një llogarije tjetër që po humb me vetëdije lojëra. - Llogaria juaj është heshtuar. - Lexoni %s tonë. Mosndjekja e udhëzimeve tona për komunikimin mund të sjellë heshtimin e llogarisë. + Llogaria juaj është heshtuar. + Lexoni %s tonë. Mosndjekja e udhëzimeve tona për komunikimin mund të sjellë heshtimin e llogarisë. Llogaria juaj është përjashtuar nga tabelat. Këtë e pkufizojmë si përdorim të çfarëdo rrute të padrejtë për të hyrë te tabela. Llogaria juaj u mbyll nga moderatorë. Blogjet tuaja janë fshehur nga moderatorë. Sigurohuni se lexoni edhe një herë %s tona. - Keni një mbarim kohe lëvizjeje. - udhëzime komunikimi + Keni një mbarim kohe lëvizjeje. + udhëzime komunikimi rregulla blogjesh diff --git a/translation/dest/appeal/tr-TR.xml b/translation/dest/appeal/tr-TR.xml index 3fe8b2d980056..e69a2d85031f2 100644 --- a/translation/dest/appeal/tr-TR.xml +++ b/translation/dest/appeal/tr-TR.xml @@ -14,7 +14,7 @@ Hesabınız moderatörler tarafından kapatılmıştır. Bloglarınız moderatörler tarafından görünmeze alınmıştır. %s bölümümüzü tekrar okuduğunuzdan emin olun. - Oyun zaman aşımınız var. + Uzaklaştırma cezanız var. iletişim kuralları blog kuralları Fair Play diff --git a/translation/dest/appeal/vi-VN.xml b/translation/dest/appeal/vi-VN.xml index bb682ac1599ea..dab9fb42283a0 100644 --- a/translation/dest/appeal/vi-VN.xml +++ b/translation/dest/appeal/vi-VN.xml @@ -8,14 +8,14 @@ Tài khoản của bạn bị đánh dấu vì thao túng xếp hạng. Chúng tôi định nghĩa điều này là cố tình thao túng xếp hạng bằng cách cố tình thua ván cờ hoặc bằng cách chơi với một tài khoản khác đang cố tình thua ván cờ. Tài khoản của bạn bị tắt tiếng. - Đọc %s của chúng tôi. Việc không tuân thủ các nguyên tắc giao tiép có thể dẫn đến việc tài khoản bị tắt tiếng. + Đọc %s của chúng tôi. Việc không tuân thủ các nguyên tắc giao tiép có thể dẫn đến việc tài khoản bị tắt tiếng. Tài khoản của bạn đã bị loại khỏi bảng xếp hạng. Chúng tôi định nghĩa điều này là sử dụng bất kỳ cách không công bằng nào để có được vị trí trên bảng xếp hạng. Tài khoản của bạn đã bị đóng bởi điều hành viên. Blog của bạn đã bị người điều hành ẩn. Hãy nhớ đọc lại %s của chúng tôi. Bạn có thời gian chờ chơi. - hướng dẫn giao tiếp + hướng dẫn giao tiếp quy tắc blog Chơi Công Bằng diff --git a/translation/dest/arena/bg-BG.xml b/translation/dest/arena/bg-BG.xml index 834aa6ed60eb1..8d7c4826575ca 100644 --- a/translation/dest/arena/bg-BG.xml +++ b/translation/dest/arena/bg-BG.xml @@ -71,8 +71,8 @@ медиани Общо Среднени точки - Сума на точките - Среднен рейтинг + Сума на точките + Среднен рейтинг Победители в турнира Само титулувани играчи Изискване на официална титла за участие в турнира diff --git a/translation/dest/arena/ca-ES.xml b/translation/dest/arena/ca-ES.xml index ac6b5213ca5f9..e1762bf2c45f3 100644 --- a/translation/dest/arena/ca-ES.xml +++ b/translation/dest/arena/ca-ES.xml @@ -77,8 +77,8 @@ Per exemple, estar en el tercer lloc en un torneig de 100 jugadors = 3%. Ser cla Totes les mitjanes d\'aquesta pàgina són %s. Total Mitjana de punts - Suma de punts - Rànquing mitjà + Suma de punts + Rànquing mitjà Guanyadors de tornejos Insígnies de tornejos Només jugadors titulats diff --git a/translation/dest/arena/da-DK.xml b/translation/dest/arena/da-DK.xml index b98f26b066ec3..818edf656b379 100644 --- a/translation/dest/arena/da-DK.xml +++ b/translation/dest/arena/da-DK.xml @@ -77,8 +77,8 @@ Eksempelvis er en rangering som nummer 3 i en turnering med 100 spillere = 3%. A Alle gennemsnit på denne side er %s. I alt Gennemsnitlige point - Sum af point - Gennemsnitlig rangering + Sum af point + Gennemsnitlig rangering Turneringsvindere Turneringsskjold Kun spillere med titel diff --git a/translation/dest/arena/de-DE.xml b/translation/dest/arena/de-DE.xml index 6837b0feb79b0..0fc28b807c32f 100644 --- a/translation/dest/arena/de-DE.xml +++ b/translation/dest/arena/de-DE.xml @@ -79,8 +79,8 @@ Ein Beispiel: Wenn man in einem Turnier von 100 Spielern Platz 3 erreicht hat = Alle Durchschnittswerte auf dieser Seite sind %s. Gesamt Punkte im Schnitt - Punkte insgesamt - Platzierungs-Durchschnitt + Punkte insgesamt + Platzierungs-Durchschnitt Turniersieger Turnier-Schilde Nur Spieler mit Titel diff --git a/translation/dest/arena/el-GR.xml b/translation/dest/arena/el-GR.xml index 5a4d1f24e897f..541e95d2285d6 100644 --- a/translation/dest/arena/el-GR.xml +++ b/translation/dest/arena/el-GR.xml @@ -79,8 +79,8 @@ To Berserk δεν ισχύει για παρτίδες με μηδενικό α Όλοι οι μέσοι όροι σε αυτή τη σελίδα είναι %s. Σύνολο Μέσος όρος πόντων - Σύνολο πόντων - Μέσος όρος κατάταξης + Σύνολο πόντων + Μέσος όρος κατάταξης Νικητές πρωταθλήματος Πρωταθλήματα Ασπίδας Μόνο τιτλούχοι παίκτες diff --git a/translation/dest/arena/en-US.xml b/translation/dest/arena/en-US.xml index 63a9057477bf2..2e354f59aff4d 100644 --- a/translation/dest/arena/en-US.xml +++ b/translation/dest/arena/en-US.xml @@ -80,8 +80,8 @@ For instance, being ranked 3 in a tournament of 100 players = 3%. Being ranked 1 All averages on this page are %s. Total Points avg - Points sum - Rank avg + Points sum + Rank avg Tournament winners Tournament shields Only titled players diff --git a/translation/dest/arena/es-ES.xml b/translation/dest/arena/es-ES.xml index ca3629e787f5e..8c6f7da1cd54d 100644 --- a/translation/dest/arena/es-ES.xml +++ b/translation/dest/arena/es-ES.xml @@ -79,8 +79,8 @@ Por ejemplo, ocupar el puesto 3 en un torneo de 100 jugadores = 3%. Estar en el Todos los promedios en esta página son %s. Total Promedio de puntos - Suma de puntos - Promedio de clasificación + Suma de puntos + Promedio de clasificación Ganadores del torneo Escudos de torneo Solo jugadores titulados diff --git a/translation/dest/arena/fi-FI.xml b/translation/dest/arena/fi-FI.xml index 2131da2173be2..83809b27f9ea9 100644 --- a/translation/dest/arena/fi-FI.xml +++ b/translation/dest/arena/fi-FI.xml @@ -77,8 +77,8 @@ Esimerkiksi sijoittuminen 3. sijalle 100 pelaajan turnauksessa = 3%. Sijoittumin Kaikki keskiarvot tällä sivulla ovat %s. Yhteensä Pisteiden keskiarvo - Pisteiden summa - Sijoitusten keskiarvo + Pisteiden summa + Sijoitusten keskiarvo Turnauksen voittajat Turnauskilvet Vain arvonimen saaneet pelaajat diff --git a/translation/dest/arena/fr-FR.xml b/translation/dest/arena/fr-FR.xml index 2df67b2616799..401be64e2dfd3 100644 --- a/translation/dest/arena/fr-FR.xml +++ b/translation/dest/arena/fr-FR.xml @@ -77,8 +77,8 @@ Par exemple, être classé 3e dans un tournoi de 100 joueurs = 3 %. Être class Toutes les moyennes sur cette page sont des %s. Total Moyenne des points - Somme des points - Classement moyen + Somme des points + Classement moyen Vainqueurs du tournoi Tournois des Boucliers Uniquement les joueurs titrés diff --git a/translation/dest/arena/gl-ES.xml b/translation/dest/arena/gl-ES.xml index 6558c4f6ddfca..ba09441d40760 100644 --- a/translation/dest/arena/gl-ES.xml +++ b/translation/dest/arena/gl-ES.xml @@ -78,8 +78,8 @@ Por exemplo, ficar en 3º lugar nun torneo de 100 xogadores = 3%. Ficar 10º nun Todas as medias nesta páxina son %s. Total Media de puntos - Suma dos puntos - Clasificación media + Suma dos puntos + Clasificación media Gañadores do torneo Torneos de Escudo Só xogadores titulados diff --git a/translation/dest/arena/he-IL.xml b/translation/dest/arena/he-IL.xml index 301d1b935d5a2..588e92fd103ef 100644 --- a/translation/dest/arena/he-IL.xml +++ b/translation/dest/arena/he-IL.xml @@ -8,7 +8,7 @@ טורניר זה אינו מדורג ולא ישפיע על דירוגך. חלק מהתחרויות הן מדורגות ויקבעו את דירוגך. איך מחושבות תוצאות המשחקים? - נצחון מזכה ב-2 נקודות, תיקו באחת, והפסד לא מזכה בנקודות. לאחר שני ניצחונות רצופים, יוכפלו הנקודות: 4 לניצחון, 2 לתיקו, 0 להפסד. מצב זה יסומן באמצעות אייקון להבה. הפסד או תיקו יסיימו את סדרת הכפלת הנקודות והניקוד יחושב אחריהם באופן רגיל. למשל, 2 נצחונות ואחריהם תיקו שווים 2+2+(2x1) או 6 נקודות + נצחון מזכה ב־2 נקודות, תיקו באחת, והפסד לא מזכה בנקודות. לאחר שני ניצחונות רצופים, יוכפלו הנקודות: 4 לניצחון, 2 לתיקו, 0 להפסד. מצב זה יסומן באמצעות אייקון להבה. הפסד או תיקו יסיימו את סדרת הכפלת הנקודות והניקוד יחושב אחריהם באופן רגיל. למשל, 2 נצחונות ואחריהם תיקו שווים 2+2+(2x1) או 6 נקודות אטרף בטורנירי זירה הבוחר באופציית האטרף מאבד מחצית מזמנו, אך ניצחון יזכה אותו בנקודה נוספת, בתנאי שעשה במשחק לפחות 7 מסעים. @@ -52,7 +52,7 @@ תאריך התחלה באזור הזמן המקומי שלך. אפשרות זו עוקפת את ההגדרה \"זמן לפני תחילת הטורניר\" אפשרו את כפתור האטרף - אפשרו לשחקנים לחלק ב-2 את הזמן בשעונם כדי לקבל עוד נקודה + אפשרו לשחקנים לחלק ב־2 את הזמן בשעונם כדי לקבל עוד נקודה אפשרו לשחקנים לנהל דיונים בחדר שיחה רצפי ניצחונות בטורניר זירה לאחר 2 ניצחונות, ניצחונות רצופים מקנים 4 נקודות במקום 2. @@ -81,8 +81,8 @@ כל הממוצעים בעמוד זה הם למעשה %s. סך הכל ממוצע הנקודות - סכום הנקודות - ממוצע הדירוג + סכום הנקודות + ממוצע הדירוג מנצחי הטורנירים מגיני הטורנירים שחקנים עטורי דרגות בלבד diff --git a/translation/dest/arena/hi-IN.xml b/translation/dest/arena/hi-IN.xml index 61ea218ff1da4..b99d06030bfa8 100644 --- a/translation/dest/arena/hi-IN.xml +++ b/translation/dest/arena/hi-IN.xml @@ -78,8 +78,8 @@ इस पृष्ठ पर सभी औसत हैं %s कुल अंक औसत - अंक योग - रैंक औसत + अंक योग + रैंक औसत टूर्नामेंट विजेता टूर्नामेंट शील्ड्स केवल शीर्षक वाले खिलाड़ी diff --git a/translation/dest/arena/lb-LU.xml b/translation/dest/arena/lb-LU.xml index 7e2cad508b18d..0b87eb85129b2 100644 --- a/translation/dest/arena/lb-LU.xml +++ b/translation/dest/arena/lb-LU.xml @@ -72,7 +72,7 @@ Spill schnell an géih zeréck an d\'Turnéieriwwersicht fir méi Partien ze spi All Duerchschnëttswäerter op dëser Säit sinn %s. Insgesamt Punktenduerchschnëtt - Punktenzomm + Punktenzomm Gewënner vum Turnéier Turnéierschëlter Just Spiller mat Titel diff --git a/translation/dest/arena/nb-NO.xml b/translation/dest/arena/nb-NO.xml index f52775d36d8d1..592488f72c2ba 100644 --- a/translation/dest/arena/nb-NO.xml +++ b/translation/dest/arena/nb-NO.xml @@ -72,8 +72,8 @@ For eksempel tilsvarer en tredjeplass i en turnering med 100 spillere 3 %. En ti Alle snittverdiene på denne siden er %s. Totalt Poengsnitt - Poengsum - Prosentrangering + Poengsum + Prosentrangering Turneringsvinnere Turneringsskjold Kun spillere med sjakktittel diff --git a/translation/dest/arena/nn-NO.xml b/translation/dest/arena/nn-NO.xml index dc2e23196ea0e..2fe9be6c6484a 100644 --- a/translation/dest/arena/nn-NO.xml +++ b/translation/dest/arena/nn-NO.xml @@ -72,8 +72,8 @@ Til dømes vil ein tredjeplass i ei turnering med 100 deltakarar gje plasserings Alle snittverdiar på denne sida er %s. Totalt Poengsnitt - Poengsum - Snittplassering + Poengsum + Snittplassering Turneringsvinnarar Turneringsskjold Berre spelarar med sjakktittel diff --git a/translation/dest/arena/pl-PL.xml b/translation/dest/arena/pl-PL.xml index 44e5094e56f51..ea235df8afb6e 100644 --- a/translation/dest/arena/pl-PL.xml +++ b/translation/dest/arena/pl-PL.xml @@ -82,8 +82,8 @@ Na przykład, będąc 3 w rankingu turnieju liczącym 100 graczy = 3%. Będąc 1 Wszystkie średnie na tej stronie to %s. Ogółem Średnia liczba punktów - Suma punktów - Średni ranking + Suma punktów + Średni ranking Zwycięzcy turnieju Tarcze turniejowe Tylko gracze z tytułem diff --git a/translation/dest/arena/pt-BR.xml b/translation/dest/arena/pt-BR.xml index da2214e071516..24207637de778 100644 --- a/translation/dest/arena/pt-BR.xml +++ b/translation/dest/arena/pt-BR.xml @@ -76,8 +76,8 @@ Por exemplo, estar classificado como 3° de um torneio de 100 jogadores = 3%. Es Todas as médias desta página são %s. Total Média de pontos - Soma dos pontos - Média de classificação + Soma dos pontos + Média de classificação Vencedores de torneio Torneios de escudo Apenas jogadores titulados diff --git a/translation/dest/arena/pt-PT.xml b/translation/dest/arena/pt-PT.xml index 5663ff32219fb..afcc89d38f6bd 100644 --- a/translation/dest/arena/pt-PT.xml +++ b/translation/dest/arena/pt-PT.xml @@ -79,8 +79,8 @@ Por exemplo, ficar em 3º lugar num torneio de 100 jogadores = 3%. Estar em 10º Todas as médias desta página são %s. Total Média de pontos - Pontos Somados - Classificação Média + Pontos Somados + Classificação Média Vencedores do torneio Escudos do torneio Apenas jogadores titulados diff --git a/translation/dest/arena/ru-RU.xml b/translation/dest/arena/ru-RU.xml index 00ae84488b09a..5dfdc3abf9404 100644 --- a/translation/dest/arena/ru-RU.xml +++ b/translation/dest/arena/ru-RU.xml @@ -76,8 +76,8 @@ Средние значения по этой странице: %s. Всего Очки в среднем - Сумма очков - Среднее занятое место + Сумма очков + Среднее занятое место Победители турнира Щиты турниров Только игроки со званиями diff --git a/translation/dest/arena/sl-SI.xml b/translation/dest/arena/sl-SI.xml index 4b6559d81dc94..ea2214563ad94 100644 --- a/translation/dest/arena/sl-SI.xml +++ b/translation/dest/arena/sl-SI.xml @@ -82,8 +82,8 @@ Na primer, 3. mesto na turnirju s 100 igralci = 3%. 10. mesto na turnirju s 1000 Vsa povprečja na tej strani so %s. Skupaj Povprečje točk - Seštevek točk - Povprečje uvrstitve + Seštevek točk + Povprečje uvrstitve Zmagovalci turnirjev Turnirski ščiti Samo igralci z nazivom diff --git a/translation/dest/arena/sq-AL.xml b/translation/dest/arena/sq-AL.xml index ade3619d72597..c0fd85dca0d6a 100644 --- a/translation/dest/arena/sq-AL.xml +++ b/translation/dest/arena/sq-AL.xml @@ -76,8 +76,8 @@ Për shembull, të qenët i renditur i 3-i në një turne me 100 lojtarë = 3%. Krejt mesataret në këtë faqe janë %s. Gjithsej Mesatare pikësh - Shumë pikësh - Mesatare renditjeje + Shumë pikësh + Mesatare renditjeje Fitues turnesh Vetëm lojtarë me titull Për të marrë pjesë në turne, kërko doemos një titull zyrtar diff --git a/translation/dest/arena/th-TH.xml b/translation/dest/arena/th-TH.xml index 461c069730dd0..d354c557c8256 100644 --- a/translation/dest/arena/th-TH.xml +++ b/translation/dest/arena/th-TH.xml @@ -68,8 +68,8 @@ ค่าเฉลี่ยทั้งหมดในหน้านี้คือ %s ทั้งหมด แต้มเฉลี่ย - ผลรวมแต้ม - อันดับเฉลี่ย + ผลรวมแต้ม + อันดับเฉลี่ย ผู้ชนะทัวร์นาเมนต์ โล่การแข่งขัน ผู้เล่นที่มีตำแหน่ง diff --git a/translation/dest/arena/uk-UA.xml b/translation/dest/arena/uk-UA.xml index 85e9d677918cc..512113ed6bd46 100644 --- a/translation/dest/arena/uk-UA.xml +++ b/translation/dest/arena/uk-UA.xml @@ -81,8 +81,8 @@ Усі середні значення на цій сторінці є %s. Загальні Середні значення очків - Сума очок - Середнє місце + Сума очок + Середнє місце Переможці турніру Турнірні щити Тільки титуловані гравці diff --git a/translation/dest/arena/vi-VN.xml b/translation/dest/arena/vi-VN.xml index ef5c287ecbbcd..f407fbe82d5f3 100644 --- a/translation/dest/arena/vi-VN.xml +++ b/translation/dest/arena/vi-VN.xml @@ -52,7 +52,7 @@ Chơi nhanh và trở lại phòng chờ để chơi được nhiều ván và g Cho phép Berserk Cho phép các kỳ thủ giảm một nửa thời gian để lấy thêm một điểm Cho phép các kỳ thủ trò chuyện trong phòng trò chuyện - Đấu trường chuỗi + Chuỗi đấu trường Sau 2 ván thắng, mỗi ván thắng liên tiếp sẽ được 4 điểm thay vì 2 điểm. Không cho phép Berserk Không có chuỗi Đấu trường @@ -77,8 +77,8 @@ Ví dụ: được hạng 3 trong một giải đấu có 100 kỳ thủ = 3%. H Tất cả các giá trị trung bình trên trang này đều là %s. Tổng Điểm trung bình - Tổng điểm - Thứ hạng trung bình + Tổng điểm + Thứ hạng trung bình Các nhà vô địch giải Các khiên giải đấu Chỉ những kỳ thủ có danh hiệu diff --git a/translation/dest/arena/zh-CN.xml b/translation/dest/arena/zh-CN.xml index f08f65439ef7e..1d6daa258883a 100644 --- a/translation/dest/arena/zh-CN.xml +++ b/translation/dest/arena/zh-CN.xml @@ -75,8 +75,8 @@ 此页面上的所有平均值是 %s。 总计 平均分数 - 总分数 - 平均等级 + 总分数 + 平均等级 锦标赛赢家 锦标赛盾牌 仅限有头衔玩家 diff --git a/translation/dest/broadcast/af-ZA.xml b/translation/dest/broadcast/af-ZA.xml index d0e649c0eeae4..9aae5c00797cc 100644 --- a/translation/dest/broadcast/af-ZA.xml +++ b/translation/dest/broadcast/af-ZA.xml @@ -14,12 +14,11 @@ Kort beskrywing van die toernooi Volle geleentheid beskrywing Opsionele lang beskrywing van die uitsending. %1$s is beskikbaar. Lengte moet minder as %2$s karakters. + PGN-Bronskakel URL wat Lichess sal nagaan vir PGN opdaterings. Dit moet openbaar beskikbaar wees vanaf die Internet. Begin datum in jou eie tydsone Optioneel, indien jy weet wanner die geleentheid begin Gee krediet aan die bron - Uitsaai bronadres - Huidige ronde se bronadres Huidige spel se bronadres Laai al die rondes af Herstel die ronde @@ -30,4 +29,13 @@ Vee beslis die hele toernooi uit, met al sy rondtes en spelle. Opsioneel: vervang spelername, graderings en titels Periode in sekondes + FIDE-federasies + Top 10 gradering + FIDE-deelnemers + FIDE-deelnemer nie gevind nie + FIDE-profiel + Federasie + Ouderdom vanjaar + Ongegradeerd + Onlangse toernooie diff --git a/translation/dest/broadcast/an-ES.xml b/translation/dest/broadcast/an-ES.xml index a0c9494602f13..9b90677dd6922 100644 --- a/translation/dest/broadcast/an-ES.xml +++ b/translation/dest/broadcast/an-ES.xml @@ -19,7 +19,5 @@ Data d\'inicio en a tuya zona horaria Opcional, si sabes quan prencipia l\'evento Cita la fuent - URL d\'a emisión - Vinclo d\'a ronda actual Vinclo d\'a partida actual diff --git a/translation/dest/broadcast/ar-SA.xml b/translation/dest/broadcast/ar-SA.xml index fa854c94789a4..d81246c170890 100644 --- a/translation/dest/broadcast/ar-SA.xml +++ b/translation/dest/broadcast/ar-SA.xml @@ -27,12 +27,12 @@ وصف موجز للبطولة الوصف الكامل الوصف الاختياري الطويل للبث. %1$s متوفر. يجب أن لا يتجاوز طول النص %2$s حرفاً. + رابط مصدر PGN URL الذي سيتحقق منه Lichess للحصول على تحديثات PGN. يجب أن يكون متاحًا للجميع على الإنترنت. + حتى 64 معرف لُعْبَة ليتشيس، مفصولة بمسافات. تاريخ البدء في المنطقة الزمنية الخاصة بك اختياري، إذا كنت تعرف متى يبدأ الحدث ائتمن المصدر - رابط البث - رابط الجولة الحالية رابط المباراة الحالية تحميل جميع المباريات إعادة ضبط هذه الجولة diff --git a/translation/dest/broadcast/az-AZ.xml b/translation/dest/broadcast/az-AZ.xml index d02db396eec5b..b591255097c86 100644 --- a/translation/dest/broadcast/az-AZ.xml +++ b/translation/dest/broadcast/az-AZ.xml @@ -17,8 +17,6 @@ Öz saat qurşağınızdakı başlama tarixi İstəyə bağlı, tədbirin başlama vaxtını bilirsinizsə Mənbəyə bax - Yayım URL-i - Hazırkı tur URL-i Hazırkı oyun URL-i Bu turu sıfırla Bu turu sil diff --git a/translation/dest/broadcast/be-BY.xml b/translation/dest/broadcast/be-BY.xml index 75968908b127e..832821c0fb46d 100644 --- a/translation/dest/broadcast/be-BY.xml +++ b/translation/dest/broadcast/be-BY.xml @@ -20,8 +20,6 @@ Дата пачатаку ў вашым часавым поясе Па жаданні, калі вы ведаеце пачатак падзеі Падзякаваць крыніцы - Спасылка на трансляцыю - Спасылка на бягучы тур Спасылка на бягучую гульню Спампаваць усе туры Скасаваць гэты тур diff --git a/translation/dest/broadcast/bg-BG.xml b/translation/dest/broadcast/bg-BG.xml index 87a40894b8942..fdb21fb10a49e 100644 --- a/translation/dest/broadcast/bg-BG.xml +++ b/translation/dest/broadcast/bg-BG.xml @@ -23,8 +23,6 @@ Дата на започване във вашата часова зона По избор, ако знаете, кога започва събитието Признателност на източника - URL на предаването - URL на настоящия гунд URL на настоящата партия Изтегли всички рундове Нулирай този рунд @@ -34,4 +32,7 @@ Изтрий този турнир Окончателно изтрий целия турнир, всичките му рундове и игри. По избор: промени имената на играчите, рейтингите и титлите + ФИДЕ федерации + ФИДЕ профил + Федерация diff --git a/translation/dest/broadcast/bs-BA.xml b/translation/dest/broadcast/bs-BA.xml index 7d537e804b639..cc667e6c3a81f 100644 --- a/translation/dest/broadcast/bs-BA.xml +++ b/translation/dest/broadcast/bs-BA.xml @@ -23,8 +23,6 @@ Datum početka po Vašoj vremenskoj zoni Neobavezno, ukoliko znate kada počinje događaj Navedite ko je zaslužan - Link za prenos - Link za trenutno kolo Link za trenutnu partiju Skinite sve runde Ponovo postavite ovo kolo diff --git a/translation/dest/broadcast/ca-ES.xml b/translation/dest/broadcast/ca-ES.xml index a57524541db57..57e70c2dbd9cc 100644 --- a/translation/dest/broadcast/ca-ES.xml +++ b/translation/dest/broadcast/ca-ES.xml @@ -23,12 +23,12 @@ Breu descripció del torneig Descripció total de l\'esdeveniment Opció de llarga descripció de l\'esdeveniment. %1$s és disponible. Ha de tenir menys de %2$s lletres. + URL origen del PGN URL que Lichess comprovarà per a obtenir actualitzacions PGN. Ha de ser públicament accessible des d\'Internet. + Fins a 64 identificadors de partides de Lichess, separades per espais. Data d\'inici en la teva zona horària Opcional, si saps quan comença l\'esdeveniment Cita la font - URL d\'emissió - URL actual de ronda URL actual de joc Baixa totes les rondes Restablir aquesta ronda @@ -43,4 +43,13 @@ Opcional: Reemplaça noms dels jugadors, puntuacions i títols Període en segons Opcional, quant de temps esperar entre sol·licituds. Mínim 2 segons, màxim 60 segons. Per defecte es gestiona automàticament en funció del nombre de visualitzadors. + Federacions FIDE + Top 10 Ràting + Jugadors FIDE + No s\'ha trobat el jugador FIDE + Perfil FIDE + Federació + Edat aquest any + Sense avaluació + Tornejos recents diff --git a/translation/dest/broadcast/ckb-IR.xml b/translation/dest/broadcast/ckb-IR.xml index 5fca9553a022d..533a78190b0b6 100644 --- a/translation/dest/broadcast/ckb-IR.xml +++ b/translation/dest/broadcast/ckb-IR.xml @@ -25,8 +25,6 @@ بەرواری دەستپێکردن لە چوارچێوەی کاتی خۆتدا بە ھەلبژاردنی خۆتە چ کاتێک دەس پێ بکات لە سەرچاوەکە دڵنیابە - لینکی پالەوانێتیەکە - لینکی خولی یەکەم لینکی یاریەکانی ئێستا دابەزاندنی ھەموو خولەکان دەسکاری کردنی ئەم خولە @@ -37,4 +35,13 @@ ئەم پاڵەوانێتییە بسڕەوە بە دڵنیاییەوە تەواوی پاڵەوانێتییەکە و هەموو خولەکانی و هەموو یارییەکانی بسڕەوە. ئارەزوومەندانە: گۆڕینی ناوی یاریزانان، هەڵسەنگاندن و نازناوەکان + فیدراسیۆنی FIDE + ڕیزبەندی ١٠ باشترینەکان + یاریزانەکانی FIDE + یاریزانی FIDE نەدۆزرایەوە + پرۆفایلی FIDE + فیدراسیۆن + تەمەنی ئەمساڵ + ڕیزبەندی نەکراوە + پاڵەوانێتییەکانی ئەم دواییە diff --git a/translation/dest/broadcast/cs-CZ.xml b/translation/dest/broadcast/cs-CZ.xml index 5827af04c0b2e..b415a32e290af 100644 --- a/translation/dest/broadcast/cs-CZ.xml +++ b/translation/dest/broadcast/cs-CZ.xml @@ -28,8 +28,6 @@ Datum a čas zahájení ve vašem časovém pásmu Nepovinné, pokud víte, kdy událost začíná Uveďte zdroj - URL adresa přenosu - URL aktuálního kola URL adresa právě probíhající partie Stáhnout hry ze všech kol Resetovat toto kolo diff --git a/translation/dest/broadcast/da-DK.xml b/translation/dest/broadcast/da-DK.xml index 9214e24fc7640..8791b6cbc953c 100644 --- a/translation/dest/broadcast/da-DK.xml +++ b/translation/dest/broadcast/da-DK.xml @@ -23,14 +23,12 @@ Kort beskrivelse af turnering Fuld beskrivelse af begivenheden Valgfri lang beskrivelse af transmissionen. %1$s er tilgængelig. Længde skal være mindre end %2$s tegn. - URL for PGN-kilde + URL for PGN-kilde URL som Lichess vil trække på for at få PGN updates. Den skal være offentlig tilgængelig fra internettet. Op til 64 Lichess parti-ID\'er, adskilt af mellemrum. Startdato i din egen tidszone Valgfri, hvis du ved, hvornår begivenheden starter Krediter kilden - Udsendelse-URL - Nuværende runde URL Nuværende parti URL Download alle runder Nulstil denne runde @@ -45,4 +43,13 @@ Valgfrit: udskift spillernavne, ratings og titler Periode i sekunder Valgfri, hvor lang tid der skal ventes mellem anmodninger. Min 2s, maks. 60s. Er som standard automatisk baseret på antallet af seere. + FIDE-føderationer + Top 10 rating + FIDE-spillere + FIDE-spiller ikke fundet + FIDE-profil + Føderation + Alder i år + Uden rating + Seneste turneringer diff --git a/translation/dest/broadcast/de-DE.xml b/translation/dest/broadcast/de-DE.xml index 54fcd97d89d31..8582eff0c95ab 100644 --- a/translation/dest/broadcast/de-DE.xml +++ b/translation/dest/broadcast/de-DE.xml @@ -23,14 +23,12 @@ Kurze Turnierbeschreibung Vollständige Ereignisbeschreibung Optionale, ausführliche Beschreibung der Übertragung. %1$s ist verfügbar. Die Beschreibung muss kürzer als %2$s Zeichen sein. - PGN Quell-URL + PGN Quell-URL URL die Lichess abfragt um PGN Aktualisierungen zu erhalten. Sie muss öffentlich aus dem Internet zugänglich sein. Bis zu 64 Lichess Partie-IDs, getrennt durch Leerzeichen. Startdatum in deiner eigenen Zeitzone Optional, falls du weißt wann das Ereignis beginnt Erwähne die Quelle - URL der Übertragung - URL der aktuellen Runde URL der aktuellen Partie Alle Runden herunterladen Diese Runde zurücksetzen @@ -45,4 +43,13 @@ Optional: Spielernamen, Wertungen und Titel ersetzen Dauer in Sekunden Optional, wie lange zwischen den Anfragen gewartet werden soll. Mindestens 2s, maximal 60s. Standardmäßig auf der Zuschaueranzahl basierend. + FIDE-Verbände + Top 10 Wertung + FIDE-Spieler + FIDE-Spieler nicht gefunden + FIDE-Profil + Verband + Alter in diesem Jahr + Ungewertet + Letzte Turniere diff --git a/translation/dest/broadcast/el-GR.xml b/translation/dest/broadcast/el-GR.xml index 5aeb0c835599f..d16573b6a01c9 100644 --- a/translation/dest/broadcast/el-GR.xml +++ b/translation/dest/broadcast/el-GR.xml @@ -26,8 +26,6 @@ Ημερομηνία έναρξης στη δική σας ζώνη ώρας Προαιρετικό, εάν γνωρίζετε πότε αρχίζει η εκδήλωση Αναφέρετε την πηγή - Διεύθυνση URL μετάδοσης - Διεύθυνση URL αυτού του γύρου Διεύθυνση URL αυτού του παιχνιδιού Λήψη όλων των γύρων Επαναφορά αυτού του γύρου @@ -37,4 +35,11 @@ Επεξεργασία μελέτης γύρου Διαγραφή αυτού του τουρνουά Σίγουρα διαγράψτε ολόκληρο τον διαγωνισμό, όλους τους γύρους του και όλα τα παιχνίδια του. + Ομοσπονδίες FIDE + Παίκτες FIDE + Δε βρέθηκε παίκτης FIDE + Προφίλ FIDE + Ομοσπονδία + Φετινή ηλικία + Πρόσφατα τουρνουά diff --git a/translation/dest/broadcast/en-US.xml b/translation/dest/broadcast/en-US.xml index 449b09b6167d1..c969a0d18f7bd 100644 --- a/translation/dest/broadcast/en-US.xml +++ b/translation/dest/broadcast/en-US.xml @@ -23,14 +23,12 @@ Short tournament description Full event description Optional long description of the broadcast. %1$s is available. Length must be less than %2$s characters. - PGN Source URL + PGN Source URL URL that Lichess will check to get PGN updates. It must be publicly accessible from the Internet. Up to 64 Lichess game IDs, separated by spaces. Start date in your own timezone Optional, if you know when the event starts Credit the source - Broadcast URL - Current round URL Current game URL Download all rounds Reset this round @@ -45,4 +43,13 @@ Optional: replace player names, ratings and titles Period in seconds Optional, how long to wait between requests. Min 2s, max 60s. Defaults to automatic based on the number of viewers. + FIDE federations + Top 10 rating + FIDE players + FIDE player not found + FIDE profile + Federation + Age this year + Unrated + Recent tournaments diff --git a/translation/dest/broadcast/eo-UY.xml b/translation/dest/broadcast/eo-UY.xml index ff7c8046daf57..977be04ab97ab 100644 --- a/translation/dest/broadcast/eo-UY.xml +++ b/translation/dest/broadcast/eo-UY.xml @@ -27,8 +27,6 @@ Komenca dato en via propra horzono Laŭvola, se vi scias, kiam komenciĝas la evento Citu la fonton - Elsenda URL - Nuna raŭnda URL Nuna luda URL Elŝuti ĉiujn raŭndojn Restarigi ĉi tiun raŭndon diff --git a/translation/dest/broadcast/es-ES.xml b/translation/dest/broadcast/es-ES.xml index 715a6e1f9aada..465fe239f8522 100644 --- a/translation/dest/broadcast/es-ES.xml +++ b/translation/dest/broadcast/es-ES.xml @@ -23,14 +23,12 @@ Breve descripción del torneo Descripción completa del evento Descripción larga opcional de la emisión. %1$s está disponible. La longitud debe ser inferior a %2$s caracteres. - URL origen del archivo PGN + URL origen del archivo PGN URL que Lichess comprobará para obtener actualizaciones PGN. Debe ser públicamente accesible desde Internet. Hasta 64 identificadores de partidas de Lichess, separados por espacios. Fecha de inicio en tu zona horaria Opcional, si sabes cuando comienza el evento Cita la fuente - Enlace de la emisión - Enlace de la ronda actual Enlace de la partida actual Descargar todas las rondas Restablecer esta ronda @@ -45,4 +43,13 @@ Opcional: reemplazar nombres de jugadores, puntuaciones y títulos Período en segundos Opcional, cuánto tiempo esperar entre peticiones. Mín. 2 s., máx. 60 s. Por defecto es automático según el número de espectadores. + Federaciones FIDE + Los 10 mejores + Jugadores FIDE + Jugador FIDE no encontrado + Perfil FIDE + Federación + Edad actual + Sin puntuación + Torneos recientes diff --git a/translation/dest/broadcast/et-EE.xml b/translation/dest/broadcast/et-EE.xml index 46a035083600f..64e4f4d38d700 100644 --- a/translation/dest/broadcast/et-EE.xml +++ b/translation/dest/broadcast/et-EE.xml @@ -17,8 +17,6 @@ Alguskuupäev sinu ajavööndis Valikuline, kui tead millal sündmus algab Viita allikale - Otseülekande URL - Preaguse vooru URL Praeguse mängu URL Lae alla kõik voorud Lähtesta see voor diff --git a/translation/dest/broadcast/eu-ES.xml b/translation/dest/broadcast/eu-ES.xml index e4e2a834d6743..6c4e49ebd18a0 100644 --- a/translation/dest/broadcast/eu-ES.xml +++ b/translation/dest/broadcast/eu-ES.xml @@ -27,8 +27,6 @@ Zure ordu-zonako hasiera data Hautazkoa, ekitaldia noiz hasten den baldin badakizu Jatorria zein den esaiguzu - Zuzeneko emankizunaren URL helbidea - Uneko txandaren URL helbidea Uneko partidaren URL helbidea Deskargatu txanda guztiak Berrezarri txanda hau diff --git a/translation/dest/broadcast/fa-IR.xml b/translation/dest/broadcast/fa-IR.xml index 9c0bb795b9ed1..1d222de5fb759 100644 --- a/translation/dest/broadcast/fa-IR.xml +++ b/translation/dest/broadcast/fa-IR.xml @@ -23,13 +23,12 @@ توضیحات کوتاه مسابقات توضیحات کامل مسابقات توضیحات بلند و اختیاری پخش همگانی. %1$s قابل‌استفاده است. طول متن باید کمتر از %2$s نویسه باشد. + وب‌نشانیِ PGN وب‌نشانی‌ای که Lichess برای دریافت به‌روزرسانی‌های PGN می‌بررسد. آن باید از راه اینترنت در دسترس همگان باشد. - تا ۶۴ نشانه بازی لیچس٬ جداشده با فاصله. + تا ۶۴ شناسه بازی لیچس٬ جداشده با فاصله. تاریخ شروع، در منطقه زمانی خودتان اختیاری است، اگر می‌دانید چه زمانی رویداد شروع می‌شود به منبع اعتبار دهید - وب‌نشانی پخش همگانی - نشانی دور کنونی نشانی بازی کنونی بارگیری همه دورها ازنوکردن این دور @@ -44,4 +43,13 @@ اختیاری: عوض کردن نام، درجه‌بندی و عنوان بازیکنان مدت در واحد ثانیه اختیاری است، چه مدت باید بین درخواست‌ها صبر کرد. حداقل 2 ثانیه، حداکثر 60 ثانیه. بر اساس تعداد بینندگان، پیشفرض‌ها، به صورت خودکار مقدار می‌گیرند. + کشورگان‌های فیده + ده درجه‌بندی برتر + بازیکنان فیده + بازیکن فیده پیدا نشد + رُخ‌نمای فیده + کشورگان + سنِ امسال + بی‌درجه‌بندی + مسابقاتِ اخیر diff --git a/translation/dest/broadcast/fi-FI.xml b/translation/dest/broadcast/fi-FI.xml index 6e54bd3b0bdf1..6ac87f8237923 100644 --- a/translation/dest/broadcast/fi-FI.xml +++ b/translation/dest/broadcast/fi-FI.xml @@ -23,14 +23,12 @@ Turnauksen lyhyt kuvaus Täysimittainen kuvaus tapahtumasta Ei-pakollinen pitkä kuvaus lähetyksestä. %1$s-muotoiluja voi käyttää. Pituus voi olla enintään %2$s merkkiä. - PGN:n lähde-URL + PGN:n lähde-URL URL, josta Lichess hakee PGN-päivitykset. Sen täytyy olla julkisesti saatavilla internetissä. Korkeintaan 64 Lichess-pelin tunnistenumeroa välilyönneillä eroteltuna. Alkamispäivämäärä omalla aikavyöhykkeelläsi Ei-pakollinen, laita jos tiedät milloin tapahtuma alkaa Mainitse lähde - Lähetyksen URL - Tämän kierroksen URL Tämän pelin URL Lataa kaikki kierrokset Nollaa tämä kierros @@ -45,4 +43,12 @@ Valinnainen: korvaa pelaajien nimet, vahvuusluvut ja arvonimet Jakso sekunteina Tarvittaessa pyyntöjen välinen odotusaika: vähintään 2 s, enintään 60 s. Oletuksena on katsojien määrään perustuva automaattinen arvo. + FIDEn liitot + Top 10 -vahvuuslukulista + FIDE-pelaajat + FIDE-pelaajaa ei löytynyt + FIDE-profiili + Kansallinen liitto + Ikä tänä vuonna + Viimeisimmät turnaukset diff --git a/translation/dest/broadcast/fr-FR.xml b/translation/dest/broadcast/fr-FR.xml index 49a3ee11bde87..c6696c5e26ca5 100644 --- a/translation/dest/broadcast/fr-FR.xml +++ b/translation/dest/broadcast/fr-FR.xml @@ -23,14 +23,12 @@ Brève description du tournoi Description complète de l\'événement Description détaillée et optionnelle de la diffusion. %1$s est disponible. La longueur doit être inférieure à %2$s caractères. - URL source de la partie en PGN + URL source de la partie en PGN URL que Lichess interrogera pour obtenir les mises à jour du PGN. Elle doit être accessible publiquement depuis Internet. Jusqu\'à 64 ID de partie Lichess séparés par des espaces. Date de début dans votre fuseau horaire Facultatif, si vous savez quand l\'événement commence Créditer la source - URL de diffusion - Ronde actuelle URL de la partie en cours Télécharger toutes les rondes Réinitialiser cette ronde @@ -45,4 +43,13 @@ Facultatif : remplacer les noms des joueurs, les classements et les titres Période en secondes Facultatif : temps d\'attente entre les requêtes. Min. 2 sec, max. 60 sec. Par défaut automatique selon le nombre de spectateurs. + Fédérations FIDE + 10 plus hauts classements + Joueurs FIDE + Joueur FIDE introuvable + Profil FIDE + Fédération + Âge cette année + Non classé + Tournois récents diff --git a/translation/dest/broadcast/ga-IE.xml b/translation/dest/broadcast/ga-IE.xml index 97bab1791832e..38356993f0fee 100644 --- a/translation/dest/broadcast/ga-IE.xml +++ b/translation/dest/broadcast/ga-IE.xml @@ -17,8 +17,6 @@ Dáta tosaigh i do chrios ama féin Roghnach, má tá a fhios agat cathain a thosóidh an ócáid Creidmheas an fhoinse - Craoladh URL - URL babhta reatha URL cluiche reatha Íoslódáil gach babhta Athshocraigh an babhta seo diff --git a/translation/dest/broadcast/gl-ES.xml b/translation/dest/broadcast/gl-ES.xml index 4852c77cbf46b..f5b6fa6af0ba6 100644 --- a/translation/dest/broadcast/gl-ES.xml +++ b/translation/dest/broadcast/gl-ES.xml @@ -16,21 +16,19 @@ En curso Proximamente Completadas - Lichess detecta o final das roldas en función das partidas de orixe. Usa esta opción se non hai orixe. + Malia que Lichess detecta o final das roldas, pódese equivocar. Usa esta opción para facelo manualmente. Nome da rolda Número de rolda Nome do torneo Breve descrición do torneo Descrición completa do evento Descrición longa opcional da retransmisión. %1$s está dispoñíbel. A lonxitude debe ser menor de %2$s caracteres. - URL de orixe do arquivo PGN + URL de orixe do arquivo PGN Ligazón que Lichess comprobará para obter actualizacións dos PGN. Debe ser publicamente accesíbel desde a Internet. Até 64 identificadores de partidas de Lichess, separados por espazos. Data de inicio na túa zona horaria Opcional, se sabes cando comeza o evento Cita a fonte - Ligazón da transmisión - Ligazón da rolda actual Ligazón da partida actual Descargar todas as roldas Restablecer esta rolda @@ -45,4 +43,13 @@ Opcional: substituír os nomes dos xogadores, as puntuacións e os títulos Período en segundos Opcional: canto tempo se agarda entre peticións. Min 2s, max 60s. Por defecto é automático baseado no número de espectadores. + Federacións FIDE + Media do top 10 + Xogadores FIDE + Xogador FIDE non atopado + Perfil FIDE + Federación + Idade actual + Sen puntuar + Torneos recentes diff --git a/translation/dest/broadcast/gsw-CH.xml b/translation/dest/broadcast/gsw-CH.xml index 7a3b2f16d796b..b868614360d4f 100644 --- a/translation/dest/broadcast/gsw-CH.xml +++ b/translation/dest/broadcast/gsw-CH.xml @@ -23,12 +23,12 @@ Churzi Turnier Beschribig Vollschtändigi Ereignisbeschribig Optionali, usfüehrlichi Beschribig vu de Überträgig. %1$s isch verfügbar. Die Beschribig muess chürzer als %2$s Zeiche si. + PGN Quälle URL URL wo Lichess abfrögt, für PGN Aktualisierige z\'erhalte. Sie muess öffentlich im Internet zuegänglich si. + Bis zu 64 Lichess Partie - IDs, trännt dur en Leerschlag. Startdatum in dinere eigene Zitzone Optional, falls du weisch, wänn das Ereignis afangt Erwähn die Quälle - Überträgigs-URL - URL vode laufende Rundi URL vode laufende Partie Alli Runde abelade Die Rundi zruggsetze @@ -43,4 +43,13 @@ Optional: Schpillernäme, Wertige und Titel weg lah Periode i Sekunde Optional, wie lang zwüsche Afrage gwartet werden söll. Minimal 2, maximal 60 Sekunde. Die Standardischtellig isch automatisch, basierend uf der Azahl vu de Zueschauer. + FIDE Wältschachverband + Top 10 Ratings + FIDE Schpiller + FIDE Schpiller nöd g\'funde + FIDE Profil + Verband + Alter i dem Jahr + Ungwertet + Aktuellschti Turnier diff --git a/translation/dest/broadcast/he-IL.xml b/translation/dest/broadcast/he-IL.xml index f938cd4c992a4..ae6cda1b66287 100644 --- a/translation/dest/broadcast/he-IL.xml +++ b/translation/dest/broadcast/he-IL.xml @@ -12,27 +12,25 @@ הקרנה ישירה חדשה הקרנות שנרשמת אליהן הסבר על הקרנות - איך להשתמש בהקרנות ב-Lichess. + איך להשתמש בהקרנות ב־Lichess. הסבב החדש יכלול את אותם התורמים והחברים כמו בסבב הקודם. הוספת סבב כרגע בקרוב שהושלמו - ליצ׳ס מאתר מתי הושלם הסבב על פי המשחקים שבקישור למהלכים בשידור חי (המקור). הפעילו את האפשרות הזאת אם אין מקור שממנו נשאבים המשחקים. + Lichess מאתר מתי הושלם הסבב על פי המשחקים שבקישור למהלכים בשידור חי (המקור). הפעילו את האפשרות הזאת אם אין מקור שממנו נשאבים המשחקים. שם סבב מספר סבב שם הטורניר תיאור הטורניר בקצרה תיאור מלא של הטורניר תיאור מפורט של הטורניר (אופציונאלי). %1$s זמין. אורך התיאור לא יעלה על %2$s תווים. - קישור המקור של ה-PGN - הקישור ש־Lichess יבדוק כדי לקלוט עדכונים ב-PGN. הוא חייב להיות פומבי ונגיש דרך האינטרנט. + קישור המקור של ה־PGN + הקישור ש־Lichess יבדוק כדי לקלוט עדכונים ב־PGN. הוא חייב להיות פומבי ונגיש דרך האינטרנט. עד 64 מזהי משחק של Lichess, מופרדים ברווחים. תאריך ההתחלה באזור הזמן שלך אופציונאלי, אם את/ה יודע/ת מתי האירוע צפוי להתחיל תן/י קרדיט למקור - הקישור להקרנה - הקישור לסבב הנוכחי הקישור למשחק הנוכחי הורדת כל הסבבים אפס את הסיבוב הזה @@ -46,5 +44,14 @@ הצגת טבלה פשוטה שמתבססת על תוצאות המשחקים אופציונאלי: החלפה של שמות השחקנים, דירוגיהם ותאריהם תדירות בשניות - אופציונאלי. משפיע על משך הזמן שעובר בין משיכות הנתונים מהמקור. בין 2 ל-60 שניות. כתלות במספר הצופים, הקצב עשוי לחזור לברירת המחדל. + אופציונאלי. משפיע על משך הזמן שעובר בין משיכות הנתונים מהמקור. בין 2 ל־60 שניות. כתלות במספר הצופים, הקצב עשוי לחזור לברירת המחדל. + איגודי FIDE + דירוג עשרת המובילים + שחקני FIDE + לא נמצא שחקן FIDE + פרופיל FIDE + איגוד + גיל השנה + לא מדורג + טורנירים אחרונים diff --git a/translation/dest/broadcast/hi-IN.xml b/translation/dest/broadcast/hi-IN.xml index d27142de5a14f..e87b3901e3a12 100644 --- a/translation/dest/broadcast/hi-IN.xml +++ b/translation/dest/broadcast/hi-IN.xml @@ -22,8 +22,6 @@ अपने स्वयं के समयक्षेत्र में प्रारंभ दिनांक वैकल्पिक, यदि आप जानना चाहते हो की प्रतिस्प्रधा कब शुरू होगी स्रोत को श्रेय दें - प्रसारण की कड़ी - वर्तमान अध्याय URL वर्तमान अध्याय URL सभी राउंड डाउनलोड करें इस फॉर्म को रीसेट करें diff --git a/translation/dest/broadcast/hr-HR.xml b/translation/dest/broadcast/hr-HR.xml index 5a8a5071df827..e41927412f7fb 100644 --- a/translation/dest/broadcast/hr-HR.xml +++ b/translation/dest/broadcast/hr-HR.xml @@ -22,8 +22,6 @@ Datum početka u vlastitoj vremenskoj zoni Neobavezno, ako znaš kada događaj počinje Naglasi izvor - Emitiraj URL - URL trenutne runde URL trenutne igre Preuzmite sve igre Resetiraj ovu rundu diff --git a/translation/dest/broadcast/hu-HU.xml b/translation/dest/broadcast/hu-HU.xml index d0adfb37758bc..20d429283f462 100644 --- a/translation/dest/broadcast/hu-HU.xml +++ b/translation/dest/broadcast/hu-HU.xml @@ -21,8 +21,6 @@ Kezdés időpontja a saját időzónádban Opcionális, ha tudod mikor kezdődik az esemény Forrásmegjelölés - Közvetítés URL - Jelenlegi forduló URL Jelenlegi játszma URL Összes játszma letöltése A forduló újrakezdése diff --git a/translation/dest/broadcast/hy-AM.xml b/translation/dest/broadcast/hy-AM.xml index 16be2356bdea2..71757a855652e 100644 --- a/translation/dest/broadcast/hy-AM.xml +++ b/translation/dest/broadcast/hy-AM.xml @@ -17,8 +17,6 @@ Սկսվելու ամսաթիվը Ձեր ժամագոտում Լրացուցիչ, եթե գիտեք, թե երբ է սկսվելու իրադարձությունը Երախտագիտություն - Հեռարձակման URL-հասցեն - Ընթացիկ խաղափուլի URL-հասցեն Ընթացիկ պարտիայի URL-հասցեն Բեռնել բոլոր խաղափուլերը Հեռացնել այս խաղափուլը diff --git a/translation/dest/broadcast/id-ID.xml b/translation/dest/broadcast/id-ID.xml index 80fb0440c0161..d91125b65a363 100644 --- a/translation/dest/broadcast/id-ID.xml +++ b/translation/dest/broadcast/id-ID.xml @@ -17,8 +17,6 @@ Tanggal mulai di zona waktu Anda sendiri Opsional, jika Anda tahu kapan acara dimulai Sertakan sumber - Tautan siaran - Tautan ronde ini Tautan permainan ini Unduh semua ronde Atur ulang ronde ini diff --git a/translation/dest/broadcast/it-IT.xml b/translation/dest/broadcast/it-IT.xml index adc748be56885..8d54528c4721d 100644 --- a/translation/dest/broadcast/it-IT.xml +++ b/translation/dest/broadcast/it-IT.xml @@ -27,8 +27,6 @@ Data di inizio nel tuo fuso orario Facoltativo, se sai quando inizia l\'evento Cita la fonte - URL della diretta - URL del turno corrente URL della partita corrente Scarica tutti i round Reimposta questo turno diff --git a/translation/dest/broadcast/ja-JP.xml b/translation/dest/broadcast/ja-JP.xml index 53bdbd425abe8..b45c916f4d6b9 100644 --- a/translation/dest/broadcast/ja-JP.xml +++ b/translation/dest/broadcast/ja-JP.xml @@ -22,12 +22,12 @@ 大会の短い説明 長い説明 内容の詳しい説明(オプション)。%1$s が利用できます。長さは [欧文換算で] %2$s 字まで。 + PGN のソース URL Lichess が PGN を取得するための URL。インターネット上に公表されているもののみ。 + Lichess ゲーム ID、半角スペースで区切って最大 64 個まで。 開始日付(あなたの現地時間) イベント開始時刻(オプション) ソースを表示する - ブロードキャスト URL - 現在のラウンドの URL 現在のゲームの URL 全ラウンドをダウンロード このラウンドをリセット @@ -42,4 +42,13 @@ オプション:プレイヤーの名前、レーティング、タイトルの変更 待機時間(秒) オプション、次のリクエストまでの待機時間を指定。最小 2 秒、最大 60 秒。デフォルト値は視聴者数から自動的に決まります。 + FIDE 加盟協会 + レーティング トップ10 + FIDE 選手 + FIDE 選手が見つかりません + FIDE プロフィール + 所属協会 + 今年時点の年齢 + レーティングなし + 最近のトーナメント diff --git a/translation/dest/broadcast/ka-GE.xml b/translation/dest/broadcast/ka-GE.xml index d5b948037c1d6..09f32253cc559 100644 --- a/translation/dest/broadcast/ka-GE.xml +++ b/translation/dest/broadcast/ka-GE.xml @@ -12,8 +12,6 @@ შეჯიბრის სახელი ტურნირი მცირე აღწერა ტურნირის სრული აღწერა - გადაცემის URL - მიმდინარე ტურის URL მიმდინარე პარტიის URL ჩამოტვირთე ყველა ტური გადატვირთე ეს ტური diff --git a/translation/dest/broadcast/kaa-UZ.xml b/translation/dest/broadcast/kaa-UZ.xml index e1e3345d0d14c..382aecc290122 100644 --- a/translation/dest/broadcast/kaa-UZ.xml +++ b/translation/dest/broadcast/kaa-UZ.xml @@ -12,8 +12,6 @@ Turnir ataması Turnirdiń qısqasha táriypi Turnirdiń tolıq táriypi - Esittiriwdiń URL mánzili - Házirgi tur URL mánzili Házirgi oyın URL mánzili Barlıq turlardı júklep alıw Bul turdı qayta ornatıw diff --git a/translation/dest/broadcast/kk-KZ.xml b/translation/dest/broadcast/kk-KZ.xml index 6406d3b24d3af..4813e2cfc3638 100644 --- a/translation/dest/broadcast/kk-KZ.xml +++ b/translation/dest/broadcast/kk-KZ.xml @@ -22,8 +22,6 @@ Басталу күні (өз уақыт белдеуіңізде) Міндетті емес, егер күнін біліп тұрсаңыз Қайнар көзіне сілтеңіз - Көрсетілім сілтемесі - Қазіргі айналым сілтемесі Қазіргі ойын сілтемесі Барлық айналымдарды жүктеп алу Бұл айналымды жаңарту diff --git a/translation/dest/broadcast/kmr-TR.xml b/translation/dest/broadcast/kmr-TR.xml index 830f1b58748e0..537cdca056584 100644 --- a/translation/dest/broadcast/kmr-TR.xml +++ b/translation/dest/broadcast/kmr-TR.xml @@ -11,8 +11,6 @@ Hejmara roundê Navê pêşbirkê Bi kurtasî di derbqrê pêşbirkî da - Lînka Weşanê - Lînka raundê niha Lînka lîstika niha Vî raundî bîne serî Vî raundî jê bibe diff --git a/translation/dest/broadcast/kn-IN.xml b/translation/dest/broadcast/kn-IN.xml index 132a6c33e8ba4..b661fc44ca339 100644 --- a/translation/dest/broadcast/kn-IN.xml +++ b/translation/dest/broadcast/kn-IN.xml @@ -21,8 +21,6 @@ ನಿಮ್ಮ ಸ್ವಂತ ಸಮಯವಲಯದಲ್ಲಿ ದಿನಾಂಕವನ್ನು ಪ್ರಾರಂಭಿಸಿ ಐಚ್ಛಿಕ, ಈವೆಂಟ್ ಯಾವಾಗ ಪ್ರಾರಂಭವಾಗುತ್ತದೆ ಎಂದು ನಿಮಗೆ ತಿಳಿದಿದ್ದರೆ ಮೂಲವನ್ನು ಕ್ರೆಡಿಟ್ ಮಾಡಿ - URL ಅನ್ನು ಪ್ರಸಾರ ಮಾಡಿ - ಪ್ರಸ್ತುತ ಸುತ್ತಿನ URL ಪ್ರಸ್ತುತ ಆಟದ URL ಎಲ್ಲಾ ಸುತ್ತುಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ ಈ ಸುತ್ತನ್ನು ಮರುಹೊಂದಿಸಿ diff --git a/translation/dest/broadcast/ko-KR.xml b/translation/dest/broadcast/ko-KR.xml index 162ee9fefe881..78dcdf8131b66 100644 --- a/translation/dest/broadcast/ko-KR.xml +++ b/translation/dest/broadcast/ko-KR.xml @@ -24,8 +24,6 @@ 본인 시간대 기준 시작일 선택사항. 언제 이벤트가 시작되는지 알고 있는 경우 출처 - 방송 URL - 현재 라운드 URL 현재 게임 URL 모든 라운드 다운로드 이 라운드 초기화 diff --git a/translation/dest/broadcast/lb-LU.xml b/translation/dest/broadcast/lb-LU.xml index fe30e6accdee3..4de7ba194bd4a 100644 --- a/translation/dest/broadcast/lb-LU.xml +++ b/translation/dest/broadcast/lb-LU.xml @@ -18,11 +18,10 @@ Komplett Turnéierbeschreiwung Optional laang Beschreiwung vum Turnéier. %1$s ass disponibel. Längt muss manner wéi %2$s Buschtawen sinn. URL déi Lichess checkt fir PGN à jour ze halen. Muss ëffentlech iwwer Internet zougänglech sinn. + Bis zu 64 Lichess-Partie-IDen, duerch Espacë getrennt. Startdatum an denger eegener Zäitzon Optional, wann du wees wéini den Turnéier ufänkt Quell kreditéieren - Iwwerdroungs-URL - URL vun der aktueller Ronn URL vun der aktueller Partie All Ronnen eroflueden Ronn zerécksetzen @@ -34,4 +33,12 @@ De ganzen Turnéier definitiv läschen, all seng Ronnen an all seng Partien. Optional: Spillernimm, Wäertungen an Titelen ersetzen Period a Sekonnen + FIDE-Federatiounen + FIDE-Spiller + FIDE-Spiller net tfonnt + FIDE-Profil + Federatioun + Alter dëst Joer + Ongewäert + Rezent Turnéieren diff --git a/translation/dest/broadcast/lt-LT.xml b/translation/dest/broadcast/lt-LT.xml index c4c5fcdf314cb..bc65f2322cfc1 100644 --- a/translation/dest/broadcast/lt-LT.xml +++ b/translation/dest/broadcast/lt-LT.xml @@ -24,8 +24,6 @@ Pradžios laikas jūsų laiko juostoje Neprivaloma; tik jeigu žinote, kada prasideda renginys Paminėkite šaltinį - Transliacijos adresas - Dabartinio raundo adresas Dabartinio žaidimo adresas Atsisiųsti visus raundus Atstatyti raundą diff --git a/translation/dest/broadcast/lv-LV.xml b/translation/dest/broadcast/lv-LV.xml index 92eac6b60cebb..fa09b699e8597 100644 --- a/translation/dest/broadcast/lv-LV.xml +++ b/translation/dest/broadcast/lv-LV.xml @@ -17,8 +17,6 @@ Sākuma datums jūsu laika joslā Neobligāts, ja zināt, kad pasākums sākas Kreditējiet avotu - Tiešraides URL - Pašreizējā raunda URL Pašreizējās spēles URL Lejupielādēt visus raundus Atiestatīt šo raundu diff --git a/translation/dest/broadcast/ml-IN.xml b/translation/dest/broadcast/ml-IN.xml index aee06d3cc0322..42cd329da8aa3 100644 --- a/translation/dest/broadcast/ml-IN.xml +++ b/translation/dest/broadcast/ml-IN.xml @@ -1,6 +1,4 @@ - പ്രക്ഷേപണം ചെയ്യുനതിനുള്ള URL - നിലവിലെ ഘട്ടത്തിന്‍റെ URL നിലവിലെ കളിയുടെ URL diff --git a/translation/dest/broadcast/mr-IN.xml b/translation/dest/broadcast/mr-IN.xml index 3111fb098a28d..562d8ea38e0d7 100644 --- a/translation/dest/broadcast/mr-IN.xml +++ b/translation/dest/broadcast/mr-IN.xml @@ -26,8 +26,6 @@ आपल्या स्वत: च्या टाइमझोनमधील तारीख पर्यायी, इव्हेंट कधी सुरू होतो हे आपल्याला माहिती असल्यास Credit the source - URL चे प्रसारण - चालू फेरीचा URL चालू खेळाचा URL सर्व फेऱ्या डाउनलोड करा ही फेरी रेसेट करा diff --git a/translation/dest/broadcast/nb-NO.xml b/translation/dest/broadcast/nb-NO.xml index 55d652db79fa9..8a03ee7d37fbf 100644 --- a/translation/dest/broadcast/nb-NO.xml +++ b/translation/dest/broadcast/nb-NO.xml @@ -23,12 +23,12 @@ Kort beskrivelse av turneringen Full beskrivelse av turneringen Valgfri lang beskrivelse av turneringen. %1$s er tilgjengelig. Beskrivelsen må være kortere enn %2$s tegn. + URL til PGN-kilden Lenke som Lichess vil hente PGN-oppdateringer fra. Den må være offentlig tilgjengelig på internett. + Opptil 64 ID-er for partier hos Lichess. De må være adskilt med mellomrom. Startdato i din egen tidssone Valgfritt, hvis du vet når arrangementet starter Krediter kilden - URL for denne overføringen - URL for denne runden URL for dette partiet Last ned alle rundene Nullstill denne runden @@ -43,4 +43,13 @@ Valgfritt: erstatt spillernavn, ratinger og titler Tidsrom i sekunder Valgfritt, hvor lenge man skal vente mellom forespørslene. Minst 2 sekunder, maks 60 sekunder. Standardinnstillingen er automatisk basert på antall seere. + FIDE-forbund + Topp 10 rating + FIDE-spillere + Fant ikke FIDE-spiller + FIDE-profil + Forbund + Alder i år + Uratet + Nylige turneringer diff --git a/translation/dest/broadcast/nl-NL.xml b/translation/dest/broadcast/nl-NL.xml index 8063d1ddd6a4c..516934639b08b 100644 --- a/translation/dest/broadcast/nl-NL.xml +++ b/translation/dest/broadcast/nl-NL.xml @@ -26,8 +26,6 @@ Aanvangsdatum in je eigen tijdzone Optioneel, als je weet wanneer het evenement start Bronvermelding - Uitzendingslink - Huidige ronde-link Huidige partij-link Alle rondes downloaden Deze ronde opnieuw instellen @@ -42,4 +40,13 @@ Optioneel: vervang spelersnamen, beoordelingen en titels Periode in seconden Optioneel, hoe lang te wachten tussen aanvragen. Minimaal 2 seconden, maximaal 60 seconden. Standaard automatisch gebaseerd op het aantal kijkers. + FIDE-federaties + Top 10-rating + FIDE-spelers + FIDE-speler niet gevonden + FIDE-profiel + Federatie + Leeftijd dit jaar + Zonder rating + Recente toernooien diff --git a/translation/dest/broadcast/nn-NO.xml b/translation/dest/broadcast/nn-NO.xml index f73a33756d29b..d262abf44feba 100644 --- a/translation/dest/broadcast/nn-NO.xml +++ b/translation/dest/broadcast/nn-NO.xml @@ -23,14 +23,12 @@ Kortfatta skildring av turneringa Full omtale av arrangementet Valfri lang omtale av overføringa. %1$s er tilgjengeleg. Omtalen må vera kortare enn %2$s teikn. - PGN kjelde-URL + PGN kjelde-URL Lenke som Lichess vil hente PGN-oppdateringar frå. Den må vera offentleg tilgjengeleg på internett. Opp til 64 Lichess spel-ID\'ar, skilde med mellomrom. Startdato i di eiga tidssone Valfritt, om du veit når arrangementet startar Kreditér kjelda - Kunngjerings-URL - URL til noverande runde URL til pågåande parti Last ned alle rundene Tilbakestill denne runden @@ -45,4 +43,13 @@ Valfritt: bytt ut spelarnamn, rangeringar og titlar Periode i sekund Ventetida mellom førespurnadene er valfri frå 2 til 60 sekund. Default tid er basert på sjåartalet. + FIDE-forbund + Topp 10 rating + FIDE-spelarar + Fann ikkje FIDE-spelar + FIDE-profil + Forbund + Alder i år + Urangert + Nylegaste turneringar diff --git a/translation/dest/broadcast/pl-PL.xml b/translation/dest/broadcast/pl-PL.xml index 62130d166a1e2..a8b9583e17ad0 100644 --- a/translation/dest/broadcast/pl-PL.xml +++ b/translation/dest/broadcast/pl-PL.xml @@ -25,14 +25,12 @@ Krótki opis turnieju Pełny opis wydarzenia Opcjonalny długi opis transmisji. %1$s jest dostępny. Długość musi być mniejsza niż %2$s znaków. - Adres URL zapisu PGN + Adres URL zapisu PGN Adres URL, który Lichess będzie udostępniał, aby można było uzyskać aktualizacje PGN. Musi być publicznie dostępny z internetu. Do 64 identyfikatorów partii, oddzielonych spacjami. Data rozpoczęcia wydarzenia w Twojej strefie czasowej Opcjonalne, jeśli wiesz kiedy wydarzenie się rozpocznie Potwierdź źródło - Adres URL transmisji - Adres URL bieżącej rundy Adres URL bieżącej partii Pobierz wszystkie rundy Zresetuj tę rundę @@ -47,4 +45,13 @@ Opcjonalnie: zmień nazwy, rankingi oraz tytuły gracza Przedział czasu w sekundach Opcjonalnie, jak długo czekać pomiędzy odpytaniami. Min 2s, max 60s. Domyślnie jest to ustawiane automatycznie na podstawie liczby widzów. + Federacje FIDE + 10 najlepszych rankingów + Zawodnicy FIDE + Nie znaleziono zawodnika FIDE + Profil FIDE + Federacja + Wiek w tym roku + Bez rankingu + Najnowsze turnieje diff --git a/translation/dest/broadcast/pt-BR.xml b/translation/dest/broadcast/pt-BR.xml index 827ac3fd43520..1b5fcf3c45826 100644 --- a/translation/dest/broadcast/pt-BR.xml +++ b/translation/dest/broadcast/pt-BR.xml @@ -23,12 +23,12 @@ Descrição curta do torneio Descrição completa do evento Descrição longa e opcional da transmissão. %1$s está disponível. O tamanho deve ser menor que %2$s caracteres. + URL de origem de PGN URL que Lichess irá verificar para obter atualizações PGN. Deve ser acessível ao público a partir da Internet. + Até 64 IDs de partidas do Lichess, separados por espaços. Data de início em seu próprio fuso horário Opcional, se você sabe quando o evento começa Crédito a fonte - URL da transmissão - URL da rodada atual URL da partida atual Baixar todas as rodadas Reiniciar esta rodada @@ -43,4 +43,13 @@ Opcional: substituir nomes de jogador, ratings e títulos Período em segundos Opcional: tempo entre as solicitações. Mín. 2s, máx. 60s. Por padrão, é calculado com base no número de espectadores. + Federações FIDE + Classificação top 10 + Jogadores FIDE + Jogador não encontrando na FIDE + Perfil FIDE + Federação + Idade atual + Sem rating + Torneios recentes diff --git a/translation/dest/broadcast/pt-PT.xml b/translation/dest/broadcast/pt-PT.xml index 098522ff13a8d..12d85abc467af 100644 --- a/translation/dest/broadcast/pt-PT.xml +++ b/translation/dest/broadcast/pt-PT.xml @@ -23,14 +23,12 @@ Breve descrição do torneio Descrição completa do evento Descrição longa do evento opcional da transmissão. %1$s está disponível. Tem de ter menos que %2$s carácteres. - URL da fonte PGN + URL da fonte PGN Link que o Lichess vai verificar para obter atualizações da PGN. Deve ser acessível ao público a partir da internet. Até 64 IDs de jogo Lichess, separados por espaços. Data de início no teu fuso horário Opcional, se souberes quando começa o evento Credita a fonte - Transmitir o link - Link da ronda atual Link da partida atual Transferir todas as rondas Reiniciar esta ronda @@ -45,4 +43,13 @@ Opcional: substituir nomes de jogadores, avaliações e títulos Período em segundos Opcional, quanto tempo de espera entre as requisições. Mínimo 2s, máximo de 60s. O padrão é automático com base no número de espetadores. + Federações FIDE + 10 melhores classificações + Jogadores FIDE + Jogador FIDE não encontrado + Perfil FIDE + Federação + Idade neste ano + Sem classificação + Torneio recentes diff --git a/translation/dest/broadcast/ro-RO.xml b/translation/dest/broadcast/ro-RO.xml index b1df2049de7e2..fabcf2f9bc6da 100644 --- a/translation/dest/broadcast/ro-RO.xml +++ b/translation/dest/broadcast/ro-RO.xml @@ -23,12 +23,12 @@ O descriere scurtă a turneului Întreaga descriere a evenimentului Descriere lungă, opțională, a difuzării. %1$s este disponibil. Lungimea trebuie să fie mai mică decât %2$s caractere. + URL sursă PGN URL-ul pe care Lichess îl va verifica pentru a obține actualizări al PGN-ului. Trebuie să fie public accesibil pe Internet. + Până la 64 de ID-uri de joc Lichess, separate prin spații. Data de începere conform fusului tău orar Opțional, dacă știi când va începe evenimentul Creditează sursa - URL pentru difuzare - URL runda curenta URL-ul partidei curente Descarcă toate rundele Resetează această rundă @@ -42,4 +42,12 @@ Calculează și afișează un clasament simplu bazat pe rezultatele jocului Opțional: înlocuiește numele jucătorilor, ratingurile și titlurile Perioada în secunde + Federații FIDE + Jucători FIDE + Jucătorul FIDE nu a fost găsit + Profil FIDE + Federație + Vârsta în acest an + Fără rating + Turnee recente diff --git a/translation/dest/broadcast/ru-RU.xml b/translation/dest/broadcast/ru-RU.xml index 4146cf59a043b..804d16ce4c5f1 100644 --- a/translation/dest/broadcast/ru-RU.xml +++ b/translation/dest/broadcast/ru-RU.xml @@ -30,8 +30,6 @@ Дата начала в вашем часовом поясе Дополнительно, если вы знаете, когда событие начнётся Признательность - URL-адрес трансляции - URL-адрес текущего тура URL-адрес текущей партии Скачать все туры Сбросить тур diff --git a/translation/dest/broadcast/ry-UA.xml b/translation/dest/broadcast/ry-UA.xml index 4625abe83fc1f..532fe812fafe3 100644 --- a/translation/dest/broadcast/ry-UA.xml +++ b/translation/dest/broadcast/ry-UA.xml @@ -17,8 +17,6 @@ Дата старта у вашум часовум поясі Опціонално, кідь знаєте коли ся зачинат припад Оддяка - Адрес трансляції - Одкликованя теперішнього тура Одкликованя теперішньої бавкы Стерьхати вшыткі рунды Перепустити сисю рунду diff --git a/translation/dest/broadcast/sk-SK.xml b/translation/dest/broadcast/sk-SK.xml index 75d8978539a7a..be0882a396e40 100644 --- a/translation/dest/broadcast/sk-SK.xml +++ b/translation/dest/broadcast/sk-SK.xml @@ -21,12 +21,12 @@ Krátky popis turnaja Úplný popis turnaja Voliteľný dlhý popis vysielania. %1$s je dostupný. Dĺžka musí byť menej ako %2$s znakov. + Zdrojová URL pre PGN súbor URL, ktorú bude Lichess kontrolovať, aby získal aktualizácie PGN. Musí byť verejne prístupná z internetu. + Až do 64 identifikátorov Lichess partií oddelených medzerami. Dátum a čas začiatku, vo vašej časovej zóne Voliteľné, ak viete kedy sa udalosť začne Uveďte zdroj - Adresa URL pre sledovanie - Adresa URL aktuálneho kola Adresa URL aktuálnej partie Stiahnuť všetky kolá Resetovať toto kolo @@ -37,4 +37,13 @@ Vymazať tento turnaj Definitívne odstrániť celý turnaj so všetkými kolami a všetkými partiami. Voliteľné: nahradiť mená hráčov, hodnotenia a tituly + FIDE federácie + 10 najlepšie hodnotených + FIDE šachisti + FIDE šachista sa nenašiel + FIDE profil + Federácia + Vek tento rok + Bez hodnotenia + Posledné turnaje diff --git a/translation/dest/broadcast/sl-SI.xml b/translation/dest/broadcast/sl-SI.xml index 719c85ee31daa..5e472972abba2 100644 --- a/translation/dest/broadcast/sl-SI.xml +++ b/translation/dest/broadcast/sl-SI.xml @@ -29,8 +29,6 @@ Datum začetka v vaše časovnem pasu Izbirno, če veste, kdaj se dogodek začne Navedi vir - URL oddaje - URL trenutnega kroga URL trenutno igrane igre Prenesite vse kroge Ponastavi ta krog diff --git a/translation/dest/broadcast/sq-AL.xml b/translation/dest/broadcast/sq-AL.xml index ff873de32923a..776a22485daf9 100644 --- a/translation/dest/broadcast/sq-AL.xml +++ b/translation/dest/broadcast/sq-AL.xml @@ -23,12 +23,12 @@ Përshkrim i shkurtër i turneut Përshkrim i plotë i turneut Përshkrim i gjatë opsional i turneut. %1$s është e disponueshme. Gjatësia duhet të jetë më pak se %2$s shenja. + URL Burimi PNG-je URL-ja që do të kontrollojë Lichess-i për të marrë përditësime PGN-sh. Duhet të jetë e përdorshme publikisht që nga Interneti. + Deri në 64 ID lojërash Lichess, ndarë me hapësira. Datë fillimi në zonën tuaj kohore Opsionale, nëse e dini kur fillon veprimtaria Atriboji merita burimit - URL Transmetimi - URL e raundit të tanishëm URL e lojës së tanishme Shkarko krejt raundet Fshije këtë raund @@ -42,4 +42,13 @@ Opsionale: zëvendësoni emra lojëtarësh, vlerësime dhe tituj Periudhë, në sekonda Opsionale, sa gjatë të pritet mes kërkesash. Min. 2s, maks. 60s. Si parazgjedhje, përdoret vlera automatike bazuar në numrin e parësve. + Federata FIDE + 10 vlerësimet kryesuese + Lojtarë FIDE + S’u gjet lojtar FIDE + Profil FIDE + Federim + Moshë këtë vit + Pa pikë + Turne së fundi diff --git a/translation/dest/broadcast/sv-SE.xml b/translation/dest/broadcast/sv-SE.xml index b7507af95de49..1fc590d49b093 100644 --- a/translation/dest/broadcast/sv-SE.xml +++ b/translation/dest/broadcast/sv-SE.xml @@ -26,8 +26,6 @@ Startdatum i din egen tidszon Valfritt, om du vet när händelsen startar Kreditera källan - Länk till direktsändning (URL) - Länk till aktuellt runda (URL) Länk till aktuellt parti (URL) Ladda ner alla omgångar Återställ den här omgången diff --git a/translation/dest/broadcast/th-TH.xml b/translation/dest/broadcast/th-TH.xml index 76255f55d2b3b..64e1a2df641a8 100644 --- a/translation/dest/broadcast/th-TH.xml +++ b/translation/dest/broadcast/th-TH.xml @@ -21,8 +21,6 @@ วันที่เริ่มในเขตเวลาของคุณ ไม่บังคับ, ถ้าคุณรู้ว่ารายการจะเริ่มเมื่อใด เครดิตแหล่ง - URL การถ่ายทอดสด - URL รอบปัจจุบัน URL เกมปัจจุบัน ตารางผู้นำอัตโนมัติ คำนวณและแสดงกระดานผู้นำอย่างง่ายตามผลลัพธ์ของเกม diff --git a/translation/dest/broadcast/tr-TR.xml b/translation/dest/broadcast/tr-TR.xml index 76e5a6567899d..de6471db7c349 100644 --- a/translation/dest/broadcast/tr-TR.xml +++ b/translation/dest/broadcast/tr-TR.xml @@ -23,12 +23,12 @@ Turnuvanın kısa tanımı Etkinliğin detaylıca açıklaması Etkinliğin isteğe bağlı detaylı açıklaması. %1$s seçeneği mevcuttur. Metnin uzunluğu azami %2$s karakter olmalıdır. + PGN Kaynak URL\'si Lichess, sağladığınız URL yardımıyla PGN\'yi güncelleyecektir. İnternet üzerinden herkese açık bir URL yazmalısınız. + Boşluklarla ayrılmış 64 adede kadar Lichess oyun ID\'si. Kendi saat diliminizdeki başlangıç zamanı İsteğe bağlı, etkinliğin ne zaman başladığını biliyorsanız ekleyebilirsiniz. Kaynağı görüntüle - Yayın linki - Şu anki turun linki Şu anki oyunun linki Bütün maçları indir Bu turu sıfırla @@ -43,4 +43,13 @@ İsteğe bağlı: Oyuncu adlarını, derecelendirmelerini ve unvanlarını değiştirin Saniye cinsinden dönem İsteğe bağlı olarak, istekler arasında beklenmesi gereken süreyi belirtin. Minimum 2 saniye, maksimum 60 saniye. Varsayılan değer, izleyici sayısına göre otomatik olarak belirlenir. + FIDE federasyonları + İlk 10 rating + FIDE oyuncuları + FIDE oyuncusu bulunamadı + FIDE profili + Federasyon + Bu yılki yaşı + Derecelendirilmemiş + Son Turnuvalar diff --git a/translation/dest/broadcast/uk-UA.xml b/translation/dest/broadcast/uk-UA.xml index d8cbe8d1075de..9dee5fc54a82d 100644 --- a/translation/dest/broadcast/uk-UA.xml +++ b/translation/dest/broadcast/uk-UA.xml @@ -25,12 +25,12 @@ Короткий опис турніру Повний опис події Необов\'язковий довгий опис трансляції. Наявна розмітка %1$s. Довжина має бути менша ніж %2$s символів. + Адреса джерела PGN Посилання, яке Lichess перевірятиме, щоб отримати оновлення PGN. Воно має бути загальнодоступним в Інтернеті. + До 64 ігрових ID Lichess, відокремлені пробілами. Дата початку у вашому часовому поясі За бажанням, якщо ви знаєте, коли починається подія Вдячність джерелу - Посилання на трансляцію - Посилання на поточний раунд Посилання на поточну гру Завантажити всі тури Скинути цей раунд @@ -45,4 +45,11 @@ За бажанням: замінити імена, рейтинги та титули гравців Період у секундах Опціонально: час очікування між запитами. Мінімально 2 с, максимально 60 с. За замовчуванням - автоматично, виходячи з кількості запитів глядачів. + Федерації FIDE + Гравці FIDE + Гравця FIDE не знайдено + Профіль FIDE + Федерація + Без рейтингу + Нещодавні турніри diff --git a/translation/dest/broadcast/vi-VN.xml b/translation/dest/broadcast/vi-VN.xml index 4c1b2fafbcbf0..316da5e8119ca 100644 --- a/translation/dest/broadcast/vi-VN.xml +++ b/translation/dest/broadcast/vi-VN.xml @@ -22,14 +22,12 @@ Mô tả ngắn giải đấu Mô tả đầy đủ giải đấu Tùy chọn mô tả dài về giải đấu. Có thể sử dụng %1$s. Độ dài phải nhỏ hơn %2$s ký tự. - URL Nguồn PGN + URL Nguồn PGN URL mà Lichess sẽ khảo sát để nhận cập nhật PGN. Nó phải được truy cập công khai từ Internet. Tối đa 64 ID ván cờ trên Lichess, phân tách bằng dấu cách. Ngày bắt đầu theo múi giờ của bạn Tùy chọn, nếu bạn biết khi nào sự kiện bắt đầu Công nhận nguồn - URL phát sóng - URL vòng đấu hiện tại URL ván đấu hiện tại Tải về tất cả ván đấu Đặt lại vòng này @@ -44,4 +42,13 @@ Tùy chọn: biệt danh, hệ số Elo và danh hiệu Khoảng thời gian tính bằng giây Tùy chọn, thời gian chờ đợi giữa các yêu cầu. Tối thiểu 2 giây, tối đa 60 giây. Mặc định là tự động dựa trên số lượng người xem. + Các liên đoàn FIDE + Hệ số Elo top 10 + Các kỳ thủ FIDE + Không tìm thấy kỳ thủ FIDE + Hồ sơ FIDE + Liên đoàn + Tuổi năm nay + Chưa xếp hạng + Các giải đấu tham gia gần đây diff --git a/translation/dest/broadcast/zh-CN.xml b/translation/dest/broadcast/zh-CN.xml index 975fa3503897a..027c600aa2274 100644 --- a/translation/dest/broadcast/zh-CN.xml +++ b/translation/dest/broadcast/zh-CN.xml @@ -26,8 +26,6 @@ 开始日期,在你的本地时区 如果你知道比赛开始时间 (可选) 信任来源 - 直播链接 - 当前一轮链接 当前棋局链接 下载所有棋局 重置此轮 @@ -42,4 +40,5 @@ 可选项:替换选手的名字、等级分和头衔 时长(秒) 可选项,请求之间等待多长时间。最小2秒,最大60秒。默认值为基于观众数量的自动值。 + 今年的年龄 diff --git a/translation/dest/broadcast/zh-TW.xml b/translation/dest/broadcast/zh-TW.xml index fbe5362588d1b..3b39eb7f68bc5 100644 --- a/translation/dest/broadcast/zh-TW.xml +++ b/translation/dest/broadcast/zh-TW.xml @@ -17,8 +17,6 @@ 開始日期 (當地時間) 可選,如果知道比賽開始時間 將來源歸因 - 直播連結 - 目前回合連結 目前棋局連結 下載所有棋局 重設此回合 diff --git a/translation/dest/challenge/he-IL.xml b/translation/dest/challenge/he-IL.xml index 03847c825b4d5..78051d8fd900f 100644 --- a/translation/dest/challenge/he-IL.xml +++ b/translation/dest/challenge/he-IL.xml @@ -9,7 +9,7 @@ לא ניתן להזמין את %s למשחק. %s לא מקבל/ת הזמנות למשחקים. הדירוג שלך ב%1$s רחוק מדי מהדירוג של %2$s. - אין אפשרות להזמין למשחק בגלל שדירוגך ב-%s זמני. + אין אפשרות להזמין למשחק בגלל שדירוגך ב־%s זמני. %s מאשר/ת רק אתגרים מחברים. אני לא מקבל/ת הזמנות עכשיו. זה לא הזמן המתאים עבורי, אנא שאל שוב מאוחר יותר. diff --git a/translation/dest/class/fa-IR.xml b/translation/dest/class/fa-IR.xml index 9a2c2455b3ada..a3961045033ce 100644 --- a/translation/dest/class/fa-IR.xml +++ b/translation/dest/class/fa-IR.xml @@ -32,7 +32,7 @@ نام واقعی خصوصی. هرگز در خارج از کلاس نمایش داده نخواهد شد. به یاد آوری دانش آموز کمک می کند. دانش‌آموز اضافه کنید - نمایه %1$s برای %2$s ساخته شد. + رُخ‌نما %1$s برای %2$s ساخته شد. دانش‌جو: %1$s نامِ کاربری: %2$s گذرواژه: %3$s diff --git a/translation/dest/class/he-IL.xml b/translation/dest/class/he-IL.xml index 5499d26895321..6cafdd08753c5 100644 --- a/translation/dest/class/he-IL.xml +++ b/translation/dest/class/he-IL.xml @@ -1,7 +1,7 @@ כיתות - למד/י כיתות של תלמידי שחמט עם כלי הכיתות ש-Lichess מעמיד לרשותך. + למד/י כיתות של תלמידי שחמט עם כלי הכיתות ש־Lichess מעמיד לרשותך. תכונות הפיקו במהירות שמות משתמשים וסיסמאות בטוחות לתלמידים עקבו אחר ההתקדמות של התלמידים במשחקים ובחידות @@ -110,14 +110,14 @@ קיבלת את ההזמנה הזו. סירבת להזמנה הזו. או - צרו חשבונות Lichess מרובים בו-זמנית + צרו חשבונות Lichess מרובים בו־זמנית את/ה יכול/ה גם %s כדי ליצור חשבונות Lichess מרובים מרשימה של שמות תלמידים. להשתמש בטופס הזה שימו לב שבכיתה יכולים להיות עד %1$s תלמידים. כדי לטפל ביותר תלמידים, %2$s. צרו עוד כיתות שמות התלמידים האמיתיים, אחד בכל שורה %s הוא עכשיו תלמיד בכיתה - נשלחה הזמנה ל-%s - ל-%s כבר יש הזמנה בהמתנה לתשובה + נשלחה הזמנה ל־%s + ל־%s כבר יש הזמנה בהמתנה לתשובה %1$s הוא חשבון של ילד/ה ולא יכול לקבל את ההודעה שלך. עליך לתת להם את קישור ההזמנה באופן ידני: %2$s diff --git a/translation/dest/coach/fa-IR.xml b/translation/dest/coach/fa-IR.xml index b8564b76ac806..b115dbf2e943a 100644 --- a/translation/dest/coach/fa-IR.xml +++ b/translation/dest/coach/fa-IR.xml @@ -14,7 +14,7 @@ دانشجوها را قبول می‌کنم در حال حاضر پذیرای دانشجویان نیستم %s مربی شطرنج دانشجویان است - نمایه %s در Lichess را ببینید + رُخ‌نما %s در Lichess را ببینید یک پیام خصوصی ارسال کنید درباره من سابقه بازی diff --git a/translation/dest/coach/he-IL.xml b/translation/dest/coach/he-IL.xml index 0ee752227f422..cd46b38638a33 100644 --- a/translation/dest/coach/he-IL.xml +++ b/translation/dest/coach/he-IL.xml @@ -1,7 +1,7 @@ - מאמנים ב-lichess - מאמן ב-lichess + מאמנים ב־lichess + מאמן ב־lichess האם את/ה מאמן/ת שחמט מעולה בדרגת %s? אמן/ית לאומי/ת או אחת מדרגות FIDE אשר/י את דרגתך כאן ואנו נבדוק את בקשתך. diff --git a/translation/dest/coach/pt-BR.xml b/translation/dest/coach/pt-BR.xml index 1aa6fd3bcd1fe..35cbc97b4a867 100644 --- a/translation/dest/coach/pt-BR.xml +++ b/translation/dest/coach/pt-BR.xml @@ -8,7 +8,7 @@ Envie-nos um e-mail para %s e nós analisaremos a sua aplicação. Local Idiomas - Pontuação + Rating Preço por hora Disponibilidade Aceita alunos diff --git a/translation/dest/contact/an-ES.xml b/translation/dest/contact/an-ES.xml index d2597792bf884..c1609c655d3db 100644 --- a/translation/dest/contact/an-ES.xml +++ b/translation/dest/contact/an-ES.xml @@ -55,9 +55,7 @@ En bels casos, cuan se chuga contra una cuenta de bot, una partida con puntuación puede que no de puntos si se determina que lo chugador ye abusando d\'o bot pa obtener puntos. Pachina d\'error Si una pachina te da error, puez reportar-la: - Quiero retransmitir un torneyo Aprende cómo puez anyadir las tuyas propias retransmisions a Lichess - Tamién puez contactar con l\'equipo de retransmisions sobre transmisions oficials. Apelar per un bloqueyo u una restricción d\'IP Motor u marca de trapacería Puez ninviar una apelación a %s. diff --git a/translation/dest/contact/ar-SA.xml b/translation/dest/contact/ar-SA.xml index faf9f5de61ba1..3ccce9a7c6d1f 100644 --- a/translation/dest/contact/ar-SA.xml +++ b/translation/dest/contact/ar-SA.xml @@ -55,9 +55,7 @@ في بعض الظروف عند اللعب ضد حساب بوت لعبة مصنفة قد لا تمنح نقاط إذا تقرر أن اللاعب يسيء استخدام البوت لنقاط التقييم. صفحة خطأ إذا واجهت صفحة خطأ، فيمكنك الإبلاغ عنها: - أريد بث بطولة تعلم كيفية بث بطولة على Lichess - بإمكانك أيضاً الاتصال بفريق البث الإذاعي عن البثوث الرسمية. طلب لرفع حظر او تقييد على IP علامة الغش او استخدام محرك يمكنك ارسال طلب الى %s. diff --git a/translation/dest/contact/be-BY.xml b/translation/dest/contact/be-BY.xml index 6ae22945e9543..d1cffbb6d4eed 100644 --- a/translation/dest/contact/be-BY.xml +++ b/translation/dest/contact/be-BY.xml @@ -55,9 +55,7 @@ У пэўных абставінах, калі гуляючы супраць уліковых запісаў ботаў, рэйтангавая партыя можа не прыводзіць да змены рэйтынгу. Калі ўстаноўлена, што гулец злоўжывае бота дзеля павялячэння рэйтынгу. Старонка памылкі Калі вы траміце на старонку з памылкай, вы можаце паведаміць пра яе: - Я хачу трансляваць турнір Дазнайцеся, як карыстацца трансляцыямі Lichess - Вы можаце звязацца з камандай трансляцый пра афіцыйныя эфіры. Абскарджанне бана ўліковага запісу ці абмежавання IP-адраса Выкарыстанне рухавіка або несумленная гульня Вы можаце накіраваць апеляцыю праз %s. diff --git a/translation/dest/contact/bg-BG.xml b/translation/dest/contact/bg-BG.xml index a210f6e5bb2e8..492dc0ab27f11 100644 --- a/translation/dest/contact/bg-BG.xml +++ b/translation/dest/contact/bg-BG.xml @@ -55,9 +55,7 @@ В някои случаи, игри с рейтинг срещу ботове могат да не донесат точки, ако система определи че играчът експлоатира бота за увеличаване на рейтинга си. Страница за грешката Ако сте се сблъскали с грешна страница, можете да съобщите за нея: - Искам да излъча турнир Научете се как да правите излъчвания в Lichess - Можете също да се свържете с екипа за излъчвания ако се интересувате от официални излъчвания. Жалба за бан или IP ограничение Програма или измама Можете да изпратите жалба до %s. diff --git a/translation/dest/contact/br-FR.xml b/translation/dest/contact/br-FR.xml index 1bcd6ec49e5ec..de20a19e202fb 100644 --- a/translation/dest/contact/br-FR.xml +++ b/translation/dest/contact/br-FR.xml @@ -28,7 +28,6 @@ Poent renkadur ebet zo bet roet Bezit sur ho peus kemeret perzh en ur c\'hrogad renket rak ar c\'hrogadoù a vignoniezh ne cheñchont ket renkadur ar c\'hoarierien. Pajenn fazi - Fellout a ra din skignañ un tournamant Galv a c\'hallit ober: %s. Arabat nac\'hañ bezañ truchet. Ma fell deoc\'h bezañ aotreet da grouiñ ur gont nevez eo gwelloc\'h deoc\'h anzav ar pezh az peus graet ha kompren pegen fall e oa. Prenañ Lichess diff --git a/translation/dest/contact/bs-BA.xml b/translation/dest/contact/bs-BA.xml index e561647bc39b7..2ac01f149c266 100644 --- a/translation/dest/contact/bs-BA.xml +++ b/translation/dest/contact/bs-BA.xml @@ -55,9 +55,7 @@ U određenim slučajevima kad igrate protiv bot računa, možda vam ocijenjena igra neće nagraditi poene ako se ustanovi da je igrač zloupotrebljavao bota da bi dobili poene. Stranica u slučaju greške Ukoliko ste dobili stranicu u slučaju greške, možete je prijaviti: - Želim prenositi turnir Naučite kako napraviti vlastita emitiranja na Lichessu - Također možete kontaktirati s timom za emitiranja u vezi sa službenim emitiranjima. Žalba na zabranu ili ograničenje IP adrese Oznaka za varalice ili za one koji koriste računar Možete poslati žalbu na %s. diff --git a/translation/dest/contact/ca-ES.xml b/translation/dest/contact/ca-ES.xml index 3c8d1f80b26ba..92268b7b7d41b 100644 --- a/translation/dest/contact/ca-ES.xml +++ b/translation/dest/contact/ca-ES.xml @@ -55,9 +55,7 @@ En alguns casos quan es juga contra un compte de bot, una partida amb puntuació pot ser que no doni punts si es determina que el jugador està abusant del bot per tal d\'obtenir punts. Pàgina d\'error Si una pàgina et dóna error, la pots reportar: - Vull retransmetre un torneig Aprèn com pots afegir les teves pròpies retransmissions a Lichess - També podeu contactar l\'equip de retransmissions per les retransmissions oficials. Apel·lació per prohibició o restricció de IP Motor o marca de trampa Pots enviar una apel·lació a %s. diff --git a/translation/dest/contact/ckb-IR.xml b/translation/dest/contact/ckb-IR.xml index 02dc28ad213bf..e585148e287e2 100644 --- a/translation/dest/contact/ckb-IR.xml +++ b/translation/dest/contact/ckb-IR.xml @@ -55,9 +55,7 @@ لە هەندێک بارودۆخدا لەکاتی یاریکردن بەرامبەر ئەکاونتی بۆت، یارییەکی ڕیزبەندی ڕەنگە خاڵت پێ نەدرێت ئەگەر بزارێت کە یاریزانەکە خراپ بۆتەکە بەکاردەهێنێت بۆ خاڵەکانی ڕیزبەندی. لاپەڕەی هەڵە ئەگەر ڕووبەڕووی لاپەڕەیەکی هەڵە بوویتەوە، دەکرێ سکاڵا بکەیت: - دەمەوێت پاڵەوانێتییەک پەخش بکەم فێربە چۆن پەخشی تایبەتی خۆت لە لیچێس دروست بکەیت - هەروەها دەتوانن پەیوەندی بە تیمی پەخشەوە بکەن سەبارەت بە پەخشی فەرمی. تانەدان بۆ قەدەغەکردن یان سنووردارکردنی IP بەکارهێنانی بزوێنەر یان نیشانەی قۆپیە کردن دەتوانی تانەدانەکە بنێریت بۆ %s. diff --git a/translation/dest/contact/cs-CZ.xml b/translation/dest/contact/cs-CZ.xml index b9e925cd7bf9e..ad2b3f2ac83f5 100644 --- a/translation/dest/contact/cs-CZ.xml +++ b/translation/dest/contact/cs-CZ.xml @@ -55,9 +55,7 @@ Za určitých okolností při hraní proti účtu bota hodnocená hra neudělí žádné body, pokud je zjištěno, že hráč zneužívá bota jen pro změnu hodnocení. Chybová stránka Pokud jste narazili na chybovou stránku, můžete ji nahlásit: - Chci vysílat turnaj Naučte se vytvořit své vlastní vysílání na Lichess - Můžete také kontaktovat vysílací tým o oficiálním vysílání. Žádost o zákaz nebo IP omezení Ozčnačení za engine nebo podvádění Můžete poslat odvolání na %s. diff --git a/translation/dest/contact/da-DK.xml b/translation/dest/contact/da-DK.xml index ea90254a620db..233d404fc67bf 100644 --- a/translation/dest/contact/da-DK.xml +++ b/translation/dest/contact/da-DK.xml @@ -55,9 +55,7 @@ Under visse omstændigheder, når du spiller mod en bot-konto, giver et ratet parti måske ikke point, hvis det fastslås, at spilleren misbruger bot\'en for ratingpoint. Fejlside Hvis du stødte på en fejlside, kan du anmelde det: - Jeg ønsker at udsende en turnering Lær hvordan du laver dine egne udsendelser på Lichess - Du kan også kontakte teamet for udsendelser angående officielle udsendelser. Appellere en udelukkelse eller IP-restriktion Skakprogram eller snyde-anmærkning Du kan sende en appel til %s. diff --git a/translation/dest/contact/de-DE.xml b/translation/dest/contact/de-DE.xml index 5d989d5269c8f..3c05f71284d0c 100644 --- a/translation/dest/contact/de-DE.xml +++ b/translation/dest/contact/de-DE.xml @@ -55,9 +55,7 @@ Wenn ein Spieler gegen einen Bot spielt, kann es unter Umständen passieren, dass ein Sieg in einem gewertetem Spiel keine Punkte einbringt, falls der Spieler den Bot dazu benutzt, Wertungspunkte anzuhäufen. Fehlerseite Falls du eine Fehlerseite entdeckt hast kannst du sie melden: - Ich möchte ein Turnier übertragen Lerne deine eigenen Übertragungen auf Lichess zu machen - Du kannst auch das Übertragungs-Team bezüglich offizieller Übertragungen kontaktieren. Einspruch gegen einen Bann oder IP-Beschränkung Markierung für Computerunterstützung/Betrug Du kannst deinen Einspruch an %s senden. diff --git a/translation/dest/contact/el-GR.xml b/translation/dest/contact/el-GR.xml index 100212a2e84ab..13fb786e168fe 100644 --- a/translation/dest/contact/el-GR.xml +++ b/translation/dest/contact/el-GR.xml @@ -55,9 +55,7 @@ Σε ορισμένες περιπτώσεις, παίζοντας εναντίον ενός λογαριασμού-υπολογιστή ένα βαθμολογημένο παιχνίδι μπορεί να μην αποφέρει πόντους βαθμολογίας εάν ο παίκτης κάνει κατάχρηση αυτού. Σελίδα σφάλματος Αν εμφανίστηκε σελίδα σφάλματος, μπορείτε να το αναφέρετε: - Θέλω να αναμεταδώσω ένα γεγονός Μάθετε πώς να κάνετε τις δικές σας μεταδόσεις στο Lichess - Μπορείτε επίσης να επικοινωνήσετε με την ομάδα μετάδοσης για επίσημες μεταδόσεις. Θέλω να αποσυρθεί ο περιορισμός/αποκλεισμός της διεύθυνσης IP μου Μηχανή ή απάτη Μπορείτε να ασκήσετε ένσταση στο %s. diff --git a/translation/dest/contact/en-US.xml b/translation/dest/contact/en-US.xml index 9036bfeeb4f69..32da03c61f5aa 100644 --- a/translation/dest/contact/en-US.xml +++ b/translation/dest/contact/en-US.xml @@ -55,9 +55,7 @@ In certain circumstances when playing against a bot account, a rated game may not award points if it is determined that the player is abusing the bot for rating points. Error page If you faced an error page, you may report it: - I want to broadcast a tournament Learn how to make your own broadcasts on Lichess - You can also contact the broadcast team about official broadcasts. Appeal for a ban or IP restriction Engine or cheat mark You may send an appeal to %s. diff --git a/translation/dest/contact/eo-UY.xml b/translation/dest/contact/eo-UY.xml index 06fe62d777ae5..45424296f1d16 100644 --- a/translation/dest/contact/eo-UY.xml +++ b/translation/dest/contact/eo-UY.xml @@ -55,9 +55,7 @@ En certaj cirkonstancoj kiam ludanta kontraŭ robotkonto, ranga ludo eble ne aljuĝas poentojn se estas determinite ke la ludanto misuzas la roboton por rangaj poentoj. Erara paĝo Se vi trovas eraran paĝon, vi povas raporti ĝin: - Mi volas elsendi turniron Lerni kiel fari viajn proprajn elsendojn ĉe Lichess - Vi ankaŭ povas kontakti la elsendan teamon pri oficialaj elsendoj. Apelacio por malpermeso aŭ IP limigo Motoro aŭ trompmarko Vi povas sendi apelacion al %s. diff --git a/translation/dest/contact/es-ES.xml b/translation/dest/contact/es-ES.xml index 9c81b75678180..4a10811bb8f41 100644 --- a/translation/dest/contact/es-ES.xml +++ b/translation/dest/contact/es-ES.xml @@ -55,9 +55,7 @@ Al jugar contra una cuenta de bot, puede que una partida por puntos no se contabilice si se determina que el jugador abusa del bot para conseguir puntos. Página de error Si una página te da error, puedes reportarla: - Quiero transmitir un torneo Aprende cómo hacer tus propias emisiones en Lichess - También puedes ponerte en contacto con el equipo de emisiones acerca de las emisiones oficiales. Apelar por un bloqueo o una restricción de IP Motor o marca de trampa Puedes enviar una apelación a %s. diff --git a/translation/dest/contact/et-EE.xml b/translation/dest/contact/et-EE.xml index b6fc69ce4849c..73b02438eba27 100644 --- a/translation/dest/contact/et-EE.xml +++ b/translation/dest/contact/et-EE.xml @@ -55,7 +55,6 @@ Teatud juhtumites mängides boti vastu reitingumäng ei anna punkte, kui on märgatud, et mängija kasutab botti ära reitingu tõstmiseks. Vigane leht Kui märkasid vigast lehte, saad teavitada sellest: - Ma soovin turniiri edastada Õpi kuidas Lichess\'is ise ülekannet teha Kaeba bännist või IP-piirangust Arvuti või pettuse märk diff --git a/translation/dest/contact/eu-ES.xml b/translation/dest/contact/eu-ES.xml index 91ec6bfad520c..59913806311f9 100644 --- a/translation/dest/contact/eu-ES.xml +++ b/translation/dest/contact/eu-ES.xml @@ -55,9 +55,7 @@ Kasu batzutan robot baten aurka jokatzean, partida batek ez ditu sailkapenerako puntuak ematen jokalaria robotarekin gehiegitan jokatzen ari bada. Errore orrialdea Errorea orrialdea agertu bazaizu, horren berri eman diezagukezu: - Txapelketa baten emanaldia egin nahi dut Ikasi nola egin emanaldiak Lichessen - Emanaldien taldearekin harremanetan jarri zaitezke emanaldi ofizialei buruz gehiago jakiteko. Debeku edo IP murriztapen baten aurka egitea Motore-erabilera edo tranpa marka Zure kexa honi bidali diezaiokezu: %s. diff --git a/translation/dest/contact/fa-IR.xml b/translation/dest/contact/fa-IR.xml index 288a9577c57ed..4412711bf884b 100644 --- a/translation/dest/contact/fa-IR.xml +++ b/translation/dest/contact/fa-IR.xml @@ -13,7 +13,7 @@ بازیابی گذرواژه را تکمیل کنید تا هویت‌سنجیِ دومِ شما برداشته شود به پشتیبانی حساب کاربری نیاز دارم می خواهم عنوانم در Lichess نشان داده شود - برای آن که عنوان شما روی نمایه Lichessتان نشان داده شود، و در میدان‌های مسابقه عنوان‌داران شرکت کنید، به صفحه تاییدِ عنوان سَر بزنید + برای آن که عنوان شما روی رُخ‌نمای Lichessتان نشان داده شود، و در میدان‌های مسابقه عنوان‌داران شرکت کنید، به صفحه تاییدِ عنوان سَر بزنید می خواهم حساب کاربری ام را ببندم شما می‌توانید در این صفحه حساب کاربری خود را ببندید از طریق رایانامه از ما نخواهید که یک حساب کاربری را ببندیم، ما آن را انجام نخواهیم داد. @@ -28,7 +28,7 @@ امکان پاک کردن پیشینه بازی‌ها، پیشینه معماها یا درجه‌بندی وجود ندارد. می‌خواهم یک کاربر را گزارش کنم برای گزارش کردنِ یک کاربر، از فرم گزارش استفاده کنید - همچنین می‌توانید با زدن روی دکمه گزارش %s در یک صفحه نمایه، به آن صفحه برسید. + همچنین می‌توانید با زدن روی دکمه گزارش %s در یک صفحه رُخ‌نما، به آن صفحه برسید. در تالار گفت و گو کاربرها را گزارش نکنید. از ارسال رایانامه به ما برای گزارش خودداری کنید. لطفا پیام مستقیم به مدیران سایت نفرستید. @@ -36,7 +36,7 @@ می‌خواهم یک ایراد فنی را گزارش کنم در قسمت بازخوردِ Lichess در تالار گفت و گو به عنوان یک مشکلِ وب‌سایت Lichess روی GitHub - به عنوان یک مشکلِ اپلیکیشن موبایل Lichess روی GitHub + مشکلِ گوشی‌افزار Lichess در GitHub در سرورِ دیسکوردِ Lichess لطفاً توصیف کنید که نقصان فنی چگونه است، شما انتظار داشتید به جای آن چه اتفاقی رخ می‌داد، و گامهایی که نقصان را مجدداً ایجاد می‌کند را شرح دهید. زدنِ غیرقانونیِ سرباز @@ -55,11 +55,9 @@ در مواقع مشخص و در بازی مقابل یک حساب کاربری ربات، اگر مشخص شود که بازیکن در حال سوءِ استفاده از ربات برای دریافت امتیاز است ممکن است به بازی امتیازی تعلق نگیرد. صفحه خطا اگر با صفحه خطا مواجه شدید، می‌توانید آن را گزارش کنید: - من مایلم که یک دوره مسابقات را پخش کنم یاد بگیرید که چطور پخش زنده خود را در لیچس بسازید - شما همینطور میتوانید با تیم پخش زنده در مورد پخش های رسمی تماس بگیرید. درخواست بازنگری برای یک ممنوعیت یا محدودیتِ IP - علامت‌گذاری استفاده از پردازشگر شطرنج یا تقلب + علامت‌گذاری استفاده از رایانه یا تقلب شما می‌توانید یک درخواست بررسی مجدد به %s ارسال کنید. مثبتهای اشتباهی حتماً گاهی اتفاق می‌افتد، و ما درباره آن متاسفیم. اگر درخواست بازنگری شما مشروع و قانونی باشد، ما در نزدیکترین زمان ممکن ممنوعیت را رفع خواهیم کرد. diff --git a/translation/dest/contact/fi-FI.xml b/translation/dest/contact/fi-FI.xml index 4a20167791d3a..491556919bf06 100644 --- a/translation/dest/contact/fi-FI.xml +++ b/translation/dest/contact/fi-FI.xml @@ -55,9 +55,7 @@ Joissakin tilanteissa bottia vastaan pelatusta pisteytetystä pelistä ei välttämättä saa pisteitä, jos pelaajan katsotaan käyttävän bottia hyväkseen pisteiden saamiseksi. Virhesivu Jos törmäsit virhesivuun, voit raportoida sen: - Haluan pitää lähetyksen turnauksesta Opi pitämään omia lähetyksiä Lichessissä - Voit myös ottaa yhteyden lähetystiimiin virallisia lähetyksiä koskien. Valita porttikiellosta tai IP-estosta Tietokone- tai huijarimerkinnästä Voit valittaa osoitteeseen %s. diff --git a/translation/dest/contact/fr-FR.xml b/translation/dest/contact/fr-FR.xml index bebcf70a158fb..548c9923c8b70 100644 --- a/translation/dest/contact/fr-FR.xml +++ b/translation/dest/contact/fr-FR.xml @@ -55,9 +55,7 @@ Dans certains cas, si vous jouez contre un bot, une partie classée ne vous donnera pas de points s\'il est déterminé que vous avez abusé du bot pour obtenir des points. Page d\'erreur Si un message d\'erreur s\'est affiché, vous pouvez le signaler : - Je souhaite diffuser un tournoi Apprenez comment réaliser vos propres diffusions sur Lichess - Vous pouvez également contacter l\'équipe de diffusion au sujet des diffusions officielles. Demande d\'appel pour un bannissement ou une restriction IP Tricherie ou utilisation d\'un programme Vous pouvez soumettre une demande d\'appel à %s. diff --git a/translation/dest/contact/gl-ES.xml b/translation/dest/contact/gl-ES.xml index 9088e9cf6d4f4..4349f2d192f0c 100644 --- a/translation/dest/contact/gl-ES.xml +++ b/translation/dest/contact/gl-ES.xml @@ -55,9 +55,7 @@ Ó xogar contra unha conta de bot, pode que unha partida puntuada non se contabilice se se determina que o xogador abusa do bot para conseguir puntos. Páxina de erro Se unha páxina dá erro, podes reportala: - Quero retransmitir un torneo Aprende a facer as túas propias retransmisións en Lichess - Tamén podes poñerte en contacto co equipo de retransmisións para as retransmisións oficiais. Apelar por un bloqueo ou unha restrición do IP Marca de trampa ou de motor de xadrez Podes mandar unha apelación a %s. diff --git a/translation/dest/contact/gsw-CH.xml b/translation/dest/contact/gsw-CH.xml index 0e6becb19946b..226a02c26c95a 100644 --- a/translation/dest/contact/gsw-CH.xml +++ b/translation/dest/contact/gsw-CH.xml @@ -55,9 +55,7 @@ Under beschtimmte Umschtänd - wänn gäge es \"Bot Konto\" gschpillt wird - chas si, dass kei Pünkt vergeh werded, wänn feschtgschtellt wird, dass de \"Bot\" für d\'Wertig missbrucht wird. Fählersite Wänn du e Fählersite entdeckt häsch, chasch sie mälde: - Ich wott es Turnier überträge Lern wie du - uf Lichess - dini eigeni Überträgig machsch - Du chasch - für offizielli Überträgige - au s\'Broadcast-Team kontaktiere. Ischpruch gäge en Usschluss oder IP-Beschränkig Markierig vu Computer Underschtützig oder Betrug Du chasch en Ischpruch a %s sände. diff --git a/translation/dest/contact/he-IL.xml b/translation/dest/contact/he-IL.xml index 7b324b85e1f9b..30cf77542ede8 100644 --- a/translation/dest/contact/he-IL.xml +++ b/translation/dest/contact/he-IL.xml @@ -35,7 +35,7 @@ רק דיווח על שחקנים באמצעות טופס הדיווח יזכה להתייחסות. אני רוצה לדווח על תקלה במדור המשוב על לִיצֶ\'ס בפורום - כבעיה (issue) בעמוד של ליצ׳ס באתר GitHub + כבעיה (issue) בעמוד של Lichess באתר GitHub כבעיה (issue) בעמוד של אפליקציית לִיצֶ\'ס בGitHub בשרת הDiscord של ליצ\'ס אנא תארו כיצד נראית התקלה, מה ציפיתם שיקרה במקום זאת ואת הצעדים לשחזור התקלה. @@ -55,9 +55,7 @@ בנסיבות מסויימות כאשר משחק הוא מול בוט, משחק מדורג עשוי לא להעניק נקודות אם ייקבע שהשחקן/ית מנצל/ת את הבוט לצבירת ניקוד. שגיאה בדף אם התמודדת עם שגיאה בדף, את/ה יכול/ה לדווח על זה: - אני רוצה לשדר טורניר - למדו כיצד ליצור הקרנות חיות של טורנירים בליצ׳ס - ניתן גם לכתוב לצוות השידורים שלנו לגבי שידורים רשמיים. + למדו כיצד ליצור הקרנות חיות של טורנירים ב־Lichess ערעור על איסור או על הגבלה בIP סימן שימוש במנוע או רמאות את/ה יכול/ה לשלוח ערעור ל%s. diff --git a/translation/dest/contact/hr-HR.xml b/translation/dest/contact/hr-HR.xml index 4652c50b908c2..46a4521937b0d 100644 --- a/translation/dest/contact/hr-HR.xml +++ b/translation/dest/contact/hr-HR.xml @@ -55,9 +55,7 @@ U određenim situacijama kad igrate na bodove protiv bot računa možda nećete dobiti bodove ako se ustanovi da je igrač zloupotrebljavao bota za dobivanja bodova. Stranica u slučaju greške Ukoliko ste dobili stranicu u slučaju greške, možete je prijaviti: - Želim prenositi turnir Naučite kako napraviti vlastita emitiranja na Lichessu - Također možete kontaktirati tim emitiranja u vezi službenih emitiranja. Žalba na zabranu ili ograničenje IP adrese Oznaka za varalice ili za one koji koriste računar Možete poslati žalbu na %s. diff --git a/translation/dest/contact/hu-HU.xml b/translation/dest/contact/hu-HU.xml index 59354321be696..9e30bbeba89c2 100644 --- a/translation/dest/contact/hu-HU.xml +++ b/translation/dest/contact/hu-HU.xml @@ -55,9 +55,7 @@ Bizonyos helyzetekben amikor a játékos bot ellen játszik, az értékelt játszma nem feltétlen oszt pontokat, ha a játékos kikövetkeztethetően visszaél a bottal szemben értékpontok miatt. Hibaoldal Ha hibaoldallal találkoztál, itt jelentheted: - Szeretnék közvetíteni egy versenyt Tudd meg, hogyan indíthatod el a saját közvetítésedet - Felveheted a kapcsolatot a közvetítő csapattal a hivatalos közvetítésekkel kapcsolatban. Fellebbezés kitiltás vagy IP cím korlátozás miatt Sakkprogram használat vagy csalás A fellebbezést a %s címre küldheted. diff --git a/translation/dest/contact/it-IT.xml b/translation/dest/contact/it-IT.xml index cc24b5de3426a..e266f8c38d533 100644 --- a/translation/dest/contact/it-IT.xml +++ b/translation/dest/contact/it-IT.xml @@ -55,9 +55,7 @@ In certe circostanze giocando contro un profilo bot, un gioco valutato potrebbe non ricompensare punti se è determinato che il giocatore stia abusando del bot per i punti di valutazione. Pagina di errore Se hai riscontrato una pagina di errore, puoi segnalarla: - Voglio trasmettere un torneo Impara come fare le tue trasmissioni su Lichess - Puoi anche contattare il team delle dirette riguardo le dirette ufficiali. Reclamo per un ban o una restrizione IP Motore o cheat mark Puoi inviare una segnalazione a %s. diff --git a/translation/dest/contact/ja-JP.xml b/translation/dest/contact/ja-JP.xml index bb7468022ed1e..1b751f2b2b209 100644 --- a/translation/dest/contact/ja-JP.xml +++ b/translation/dest/contact/ja-JP.xml @@ -55,9 +55,7 @@ ボットアカウントとの対戦で、ボットを利用してレーティングを操作していると判断された場合は、レーティングの点が与えられません。 エラーページ エラーページに出会ったら、以下で報告できます。 - トーナメントを配信したい Lichess で独自の配信を行なう方法について - 公式の配信については配信担当者に連絡することもできます。 アクセス禁止や IP 制限への異議申し立て 不正行為の赤マーク 異議申し立てを %s に送ることができます。 diff --git a/translation/dest/contact/kk-KZ.xml b/translation/dest/contact/kk-KZ.xml index 7c908615532b3..bf945105f3a3f 100644 --- a/translation/dest/contact/kk-KZ.xml +++ b/translation/dest/contact/kk-KZ.xml @@ -55,9 +55,7 @@ Кейде роботқа қарсы бағалы ойындарда ұпай берілмеуі мүмкін. Мысалы, ойыншы өз ұпайларын өсіру мақсатында роботты асыра пайдаланғанда. Ақаулық парақшасы Ақаулық парақшасына тап болсаңыз, осында хабарлауыңызға болады: - Мен жарысты таратқым келеді Личестегі өз көрсетіліміңізді қалай таратуға болатынын меңгеріп алыңыз - Сонымен қатар ресми тарату мәселесін тарату тобымен талқыласаңыз болады. Шектеуге не IP шектеуге қарсылық шағымы Есептеуіш не чит қолданды Сіз өтінішті %s жібере аласыз. diff --git a/translation/dest/contact/kn-IN.xml b/translation/dest/contact/kn-IN.xml index 54127ae408a4c..2a9afde215520 100644 --- a/translation/dest/contact/kn-IN.xml +++ b/translation/dest/contact/kn-IN.xml @@ -55,9 +55,7 @@ ಕೆಲವು ಸಂದರ್ಭಗಳಲ್ಲಿ ಬೋಟ್ ಖಾತೆಯ ವಿರುದ್ಧ ಆಡುವಾಗ, ಆಟಗಾರನು ರೇಟಿಂಗ್ ಪಾಯಿಂಟ್‌ಗಳಿಗಾಗಿ ಬೋಟ್ ಅನ್ನು ದುರುಪಯೋಗಪಡಿಸಿಕೊಳ್ಳುತ್ತಿದ್ದಾರೆ ಎಂದು ನಿರ್ಣಯಿಸಿದರೆ ರೇಟ್ ಮಾಡಲಾದ ಆಟವು ಅಂಕಗಳನ್ನು ನೀಡುವುದಿಲ್ಲ. ದೋಷ ಪುಟ ನೀವು ದೋಷ ಪುಟವನ್ನು ಎದುರಿಸಿದರೆ, ನೀವು ಅದನ್ನು ವರದಿ ಮಾಡಬಹುದು: - ನಾನು ಪಂದ್ಯಾವಳಿಯನ್ನು ಪ್ರಸಾರ ಮಾಡಲು ಬಯಸುತ್ತೇನೆ Lichess ನಲ್ಲಿ ನಿಮ್ಮ ಸ್ವಂತ ಪ್ರಸಾರವನ್ನು ಹೇಗೆ ಮಾಡಬೇಕೆಂದು ತಿಳಿಯಿರಿ - ಅಧಿಕೃತ ಪ್ರಸಾರಗಳ ಕುರಿತು ನೀವು ಪ್ರಸಾರ ತಂಡವನ್ನು ಸಹ ಸಂಪರ್ಕಿಸಬಹುದು. ನಿಷೇಧ ಅಥವಾ IP ನಿರ್ಬಂಧಕ್ಕಾಗಿ ಮನವಿ ಎಂಜಿನ್ ಅಥವಾ ಮೋಸ ಗುರುತು ನೀವು %s ಗೆ ಮನವಿಯನ್ನು ಕಳುಹಿಸಬಹುದು. diff --git a/translation/dest/contact/ko-KR.xml b/translation/dest/contact/ko-KR.xml index b7c3dc6d1a07d..38b881b30a2e5 100644 --- a/translation/dest/contact/ko-KR.xml +++ b/translation/dest/contact/ko-KR.xml @@ -55,9 +55,7 @@ 특정 상황에서 봇 계정을 상대로 플레이할 때, 플레이어가 레이팅을 얻기 위해 부정행위를 하는 것으로 판단될 경우 레이팅 포인트가 부여되지 않을 수 있습니다. 에러 페이지 에러 페이지가 나온 경우 여기에서 신고해주세요: - 대회를 방송하고 싶습니다 Lichess에서 나만의 방송을 하는 방법을 알아보세요 - 또한 공식 방송에 대해 방송 팀에 문의할 수도 있습니다. 차단 또는 IP 제한에 대한 이의를 제기하고 싶습니다 엔진 또는 부정행위 표시 %s에서 이의를 제기할 수 있습니다. diff --git a/translation/dest/contact/lb-LU.xml b/translation/dest/contact/lb-LU.xml index f4c9369168088..5cf27b48e52db 100644 --- a/translation/dest/contact/lb-LU.xml +++ b/translation/dest/contact/lb-LU.xml @@ -49,7 +49,6 @@ Vergewësser dech, dass du eng gewäerte Partie gespillt hues. Ongewäerte Partië beaflossen d\'Wäertung net. Feelersäit Wann s du op eng Feelersäit gestouss bass, kanns du se mellen: - Ech wëll een Turnéier iwwerdroen Asproch géint een Bann oder eng IP Restriktioun Engine- oder Bedruchsmarkéierung Du kanns däin Asproch un %s schécken. diff --git a/translation/dest/contact/lt-LT.xml b/translation/dest/contact/lt-LT.xml index eff2f0c9b7825..3ec7c133009e5 100644 --- a/translation/dest/contact/lt-LT.xml +++ b/translation/dest/contact/lt-LT.xml @@ -55,9 +55,7 @@ Tam tikromis aplinkybėmis, jei nustatoma, kad žmogus išnaudoja robotą norėdamas gauti daugiau reitingo taškų, reitinguotas žaidimas prieš roboto paskyrą gali nesuteikti reitingo taškų. Klaidos puslapis Jei susidūrėte su klaidos puslapiu galite apie jį pranešti: - Noriu transliuoti turnyrą Išmokite kurti savo transliacijas per Lichess - Dėl oficialių transliacijų galite susisiekti su transliacijų komanda. Apeliuoti blokavimą ar IP adreso apribojimą Variklio ar sukčiavimo žymė Apeliaciją galite siųsti %s. diff --git a/translation/dest/contact/lv-LV.xml b/translation/dest/contact/lv-LV.xml index 7ee61bfea2719..3b400fdb99c4a 100644 --- a/translation/dest/contact/lv-LV.xml +++ b/translation/dest/contact/lv-LV.xml @@ -55,9 +55,7 @@ Noteiktos gadījumos, spēlējot pret datorprogrammu, par vērtētām spēlēm nepiešķir punktus, ja ir noteikts, ka spēlētājs ļaunprātīgi izmanto programmu, lai paaugstinātu reitingu. Kļūdas lapa Ja saskārāties ar kļūdas lapu, varat par to ziņot: - Vēlos pārraidīt turnīru Uzzini kā veidot savas pārraides Lichess-ā - Vari arī sazināties ar pārraižu komandu par oficiālajām pārraidēm. Pārsūdzēt liegumu vai IP ierobežojumu Dzinēja vai šmaukšanās atzīme Varat iesniegt lūgumu %s. diff --git a/translation/dest/contact/nb-NO.xml b/translation/dest/contact/nb-NO.xml index 4793a1e449889..33275a4871f32 100644 --- a/translation/dest/contact/nb-NO.xml +++ b/translation/dest/contact/nb-NO.xml @@ -55,9 +55,7 @@ Dersom det blir slått fast at en spiller misbruker en botkonto for å manipulere ratingpoengene, kan det hende det ikke blir gitt poeng for partiet. Feilside Hvis du støtte på en feilside, kan du rapportere det: - Jeg vil overføre en turnering Lær hvordan du lager overføringer hos Lichess - Du kan ellers kontakte overføringsgruppen angående offisielle overføringer. Klage ved utestenging eller IP-restriksjon Anmerkning om juks eller maskinbruk Du kan klage til %s. diff --git a/translation/dest/contact/nl-NL.xml b/translation/dest/contact/nl-NL.xml index c617a9e472804..73d34f644d870 100644 --- a/translation/dest/contact/nl-NL.xml +++ b/translation/dest/contact/nl-NL.xml @@ -55,9 +55,7 @@ Wanneer een speler tegen een bot-account speelt, worden onder bepaalde omstandigheden geen ratingpunten toegekend wanneer blijkt dat de speler de bot misbruikt om ratingpunten te winnen. Foutpagina Als u te maken kreeg met een foutpagina, kunt u dat melden: - Ik wil een tornooi uitzenden Lees hoe je je eigen uitzendingen op Lichess maakt - Je kunt ook het uitzendingsteam contacteren over officiële uitzendingen. Vragen om een ban of IP beperking Schaakcomputer of valsspel markering U kunt een verzoek sturen naar %s. diff --git a/translation/dest/contact/nn-NO.xml b/translation/dest/contact/nn-NO.xml index cb8ed04bbe967..976596bc9f23d 100644 --- a/translation/dest/contact/nn-NO.xml +++ b/translation/dest/contact/nn-NO.xml @@ -55,9 +55,7 @@ Dersom det vert slått fast at ein spelar misbrukar ein bot-konto for å manipulera ratingpoeng, kan det hende det ikkje vert gjeven poeng for spelet. Feilside Dersom du støtte på ei feilside kan du rapportere det: - Eg vil kringkaste ei turnering Lær deg korleis du lagar eigne sendingar på Lichess - Du kan òg ta kontakt med kringkastingsgruppa om det som gjeld offisielle sendingar. Protest ved utestenging eller IP-restriksjon Merknad om juks eller bruk av sjakkcomputer Du kan sende ein appell til %s. diff --git a/translation/dest/contact/pl-PL.xml b/translation/dest/contact/pl-PL.xml index 2a898555eeebe..ec53a6acca551 100644 --- a/translation/dest/contact/pl-PL.xml +++ b/translation/dest/contact/pl-PL.xml @@ -55,9 +55,7 @@ W pewnych okolicznościach, grając przeciwko botowi, partia rankingowa może nie przynieść punktów, jeśli zostanie rozpoznane, że gracz nadużywa bota do nabijania ich sobie. Strona błędu Jeśli znalazłeś się na stronie błędu, możesz to zgłosić: - Chcę transmitować turniej Dowiedz się, jak tworzyć własne transmisje na Lichess - Możesz również skontaktować się z zespołem nadawczym w sprawie oficjalnych transmisji. Odwołanie od zablokowania konta lub ograniczenia IP Silnik szachowy lub inne oszustwo Możesz wysłać odwołanie do %s. diff --git a/translation/dest/contact/pt-BR.xml b/translation/dest/contact/pt-BR.xml index 6d1049360ebd1..fac6ece9bd1e1 100644 --- a/translation/dest/contact/pt-BR.xml +++ b/translation/dest/contact/pt-BR.xml @@ -55,9 +55,7 @@ Em certos casos, ao jogar contra uma conta bot, um jogo ranqueado poderá não valer pontos, se for determinado que o jogador está abusando do bot para conseguir pontos. Página de erro Se você encontrou uma página de erro, você pode reportá-la: - Quero transmitir um torneio Aprenda a fazer suas próprias transmissões no Lichess - Você também pode contatar a equipe de transmissão para saber mais sobre transmissões oficiais. Apelação de banimento ou restrição de IP Assistência de software de xadrez Você pode enviar uma apelação para %s. diff --git a/translation/dest/contact/pt-PT.xml b/translation/dest/contact/pt-PT.xml index d052d6b276448..fa4c4532bd66d 100644 --- a/translation/dest/contact/pt-PT.xml +++ b/translation/dest/contact/pt-PT.xml @@ -55,9 +55,7 @@ Em certas circunstâncias, ao jogar contra uma conta bot, uma partida a valer pontos pode não premiar pontos se for determinado que o jogador está abusando do bot para ganhar pontos. Página de erro Se encontras uma página de erro, podes reportá-lo: - Eu quero transmitir um torneio Aprenda a fazer as suas próprias transmissões no Lichess - Também pode entrar em contato com a equipa de transmissão sobre transmissões oficiais. Apelar a um banimento ou restrição de IP Marcado por fazer batota ou usar a ajuda do computador Podes enviar um apelo para %s. diff --git a/translation/dest/contact/ro-RO.xml b/translation/dest/contact/ro-RO.xml index a234320501449..8d82bbaa6f49b 100644 --- a/translation/dest/contact/ro-RO.xml +++ b/translation/dest/contact/ro-RO.xml @@ -55,9 +55,7 @@ În anumite circumstanțe, atunci când se joacă cu un cont de bot, un joc evaluat nu poate acorda puncte dacă se stabilește că jucătorul abuzează de bot pentru a obține puncte de evaluare. Pagina de eroare Dacă ai primit o pagină de eroare, o poți raporta: - Vreau să transmit un turneu Învață cum să îți faci propriile transmisiuni pe Lichess - De asemenea, puteți contacta echipa de difuzare despre emisiunile oficiale. Aplică pentru un ban sau restricție de IP Motor de asistență sau marcaj de trișare Poți trimite o cerere pe %s. diff --git a/translation/dest/contact/ru-RU.xml b/translation/dest/contact/ru-RU.xml index 995d67d796873..abe576325af3c 100644 --- a/translation/dest/contact/ru-RU.xml +++ b/translation/dest/contact/ru-RU.xml @@ -55,9 +55,7 @@ В рейтинговых играх против ботов в некоторых случаях очки рейтинга не начисляются, если система считает, что игрок эксплуатирует слабости бота для набивания рейтинга. Сообщение об ошибке Если вы столкнулись с сообщением об ошибке, вы можете сообщить об этом: - Я хочу стримить турнир Узнайте, как создавать свои собственные стримы на Lichess - Вы также можете связаться с командой поддержки стримов по вопросам ваших стримов. Обжалование бана аккаунта или ограничения IP-адреса Отметка об использовании шахматных движков или читерстве Вы можете отправить апелляцию на %s. diff --git a/translation/dest/contact/si-LK.xml b/translation/dest/contact/si-LK.xml index 41c2987dc6fd3..acabef12753b1 100644 --- a/translation/dest/contact/si-LK.xml +++ b/translation/dest/contact/si-LK.xml @@ -53,7 +53,6 @@ Rating ප්‍රදානය කර නැත ඔබ Rated ක්‍රීඩාවක් ක්‍රීඩා කළ බවට වග බලා ගන්න. Casual ක්‍රීඩා ක්‍රීඩකයන්ගේ Rating වලට​ බලපාන්නේ නැත. දෝෂ පිටුව - මට තරඟාවලියක් විකාශනය කිරීමට අවශ්‍යයි Lichess හි ඔබේම විකාශන කරන්නේ කෙසේදැයි ඉගෙන ගන්න තහනමක් හෝ IP සීමාවක් සඳහා අභියාචනය කරන්න එන්ජිම හෝ වංචා ලකුණ diff --git a/translation/dest/contact/sk-SK.xml b/translation/dest/contact/sk-SK.xml index 908bd87391fb0..654fd9663f87f 100644 --- a/translation/dest/contact/sk-SK.xml +++ b/translation/dest/contact/sk-SK.xml @@ -55,9 +55,7 @@ Za určitých okolností, keď hráte proti botovému účtu, nemusia byť za hodnotenú partiu udelené body, ak sa zistí, že hráč zneužíva bota na zisk bodov. Chybová stránka Ak ste narazili na chybovú stránku, môžete ju nahlásiť: - Chcem vysielať turnaj Naučte sa vytvárať vlastné vysielanie na stránke Lichess - V súvislosti s oficiálnymi prenosmi môžete tiež kontaktovať vysielací tím. Požiadať o zabanovanie hráča alebo obmedzenie IP adresy Značka motora alebo podvádzania Môžete poslať odvolanie na %s. diff --git a/translation/dest/contact/sl-SI.xml b/translation/dest/contact/sl-SI.xml index 6061a04f05215..402ad9265fb68 100644 --- a/translation/dest/contact/sl-SI.xml +++ b/translation/dest/contact/sl-SI.xml @@ -55,9 +55,7 @@ V določenih okoliščinah pri igranju proti robotskemu računu rejtingirana igra ne sme dodeliti točk, če se ugotovi, da igralec zlorablja robota za rejtingiranje. Stran z napako Če ste soočeni s stranjo z napako, jo lahko prijavite: - Želim prenašati turnir Naučite se narediti lastne oddaje na Lichessu - Glede uradnih oddaj se lahko obrnete tudi na ekipo oddaje. Pritožba na prepoved ali omejitev IP Oznaka za goljufa oziroma uporabnika računalniške pomoči Pritožbo lahko pošljete na %s. diff --git a/translation/dest/contact/sq-AL.xml b/translation/dest/contact/sq-AL.xml index 95ceca72e8cad..5e56ab4f2ee8e 100644 --- a/translation/dest/contact/sq-AL.xml +++ b/translation/dest/contact/sq-AL.xml @@ -55,9 +55,7 @@ Në ca rrethana, kur luhet kundër një llogarie robot, një lojë me pikë mund të mos sjellë pikë, nëse përcaktohet se lojtari po abuzon robotin për pikë vlerësimi. Faqe gabimi Nëse hasët një faqe gabimi, mund ta raportoni: - Dua të transmetoj një turne Mësoni se si të krijoni transmetimet tuaja në Lichess - Mundeni edhe të lidheni me ekipin e transmetimeve, rreth transmetimesh zyrtare. Apeloni për dëbim ose kufizim IP-je Program ose shenje hileje Mund të dërgoni një apel tek %s. diff --git a/translation/dest/contact/sv-SE.xml b/translation/dest/contact/sv-SE.xml index aa11deb932532..150634cbb047b 100644 --- a/translation/dest/contact/sv-SE.xml +++ b/translation/dest/contact/sv-SE.xml @@ -55,9 +55,7 @@ När man spelar mot ett bot-konto kan ett rankat parti under vissa omständigheter inte ge några ranking-poäng. Detta om det fastslås att spelaren missbrukar boten för att manipulera sin rating. Felsida Om du stöter på en felsida, kan du rapportera det: - Jag vill sända en turnering Lär dig hur du gör dina egna sändningar på Lichess - Du kan också kontakta sändningsteamet om officiella sändningar. Överklaga en avstängning eller IP-begränsning Schackmotor eller fuskstämpel Du kan skicka ett överklagande till %s. diff --git a/translation/dest/contact/th-TH.xml b/translation/dest/contact/th-TH.xml index 862495899ca06..a3ead2cbc67fb 100644 --- a/translation/dest/contact/th-TH.xml +++ b/translation/dest/contact/th-TH.xml @@ -55,9 +55,7 @@ ในบางสถานการณ์เมื่อเล่นกับบัญชีบอท เกมที่คิดคะแนนอาจไม่ให้คะแนนหากมีการตัดสินว่าผู้เล่นใช้บอทในทางที่ผิดเพื่อให้ได้แต้มคะแนน หน้านี้มีข้อผิดพลาด ถ้าหน้าที่คุณเปิดมีปัญหา คุณสามารถรายงานได้ - ฉันต้องการที่จะออกอากาศทัวร์นาเมนต์ เรียนรู้การออกอากาศใน Lichess - คุณก็สามารถติดต่อทีมออกอากาศในเรื่องการออกอากาศแบบเป็นทางการ การยื่นอุทธรณ์สำหรับผู้โดนแบน ระบุว่าใช้เครื่องจักรหรือโกง อนุญาติให้ยื่นอุทธรณ์แก่ %s diff --git a/translation/dest/contact/tr-TR.xml b/translation/dest/contact/tr-TR.xml index a9334712b5eeb..8a23929712809 100644 --- a/translation/dest/contact/tr-TR.xml +++ b/translation/dest/contact/tr-TR.xml @@ -55,9 +55,7 @@ Bot hesaplar ile oynanan bazı puanlı oyunlarda, bir oyuncunun puan kazanmak için botu kötüye kullandığı tespit edilirse bu oyuncu oyundan puan alamayabilir. Hata sayfası Bir hata mesajı alırsanız lütfen bize bildirin: - Bir turnuva yayını yapmak istiyorum Lichess üzerinde kendi yayınlarınızı nasıl yapacağınızı öğrenin - Ayrıca resmi yayınlar hakkında yayın ekibiyle iletişime geçebilirsiniz. Yasaklamaya itiraz et Satranç motoru ya da hile tespit edildi Başvurunuzu bu adrese gönderebilirsiniz: %s . diff --git a/translation/dest/contact/uk-UA.xml b/translation/dest/contact/uk-UA.xml index ab112243897a1..5e63b727b8651 100644 --- a/translation/dest/contact/uk-UA.xml +++ b/translation/dest/contact/uk-UA.xml @@ -55,9 +55,7 @@ За певних обставин при грі проти облікового запису бота, гра з рейтингом не може обраховувати очки, якщо система визначить, що гравець зловживає ботом для отримання рейтингових очок. Сторінка помилки Якщо ви зіткнулися з повідомленням про помилку, ви можете повідомити про це: - Я хочу транслювати турнір Дізнайтеся, як робити власні трансляції на Lichess - Також ви можете зв\'язатися з командою проведення трансляцій щодо офіційних етерів. Оскарження бану або обмеження за IP-адресою Відмітка про використання шахового рушія або нечесну гру Ви можете надіслати оскарження на %s. diff --git a/translation/dest/contact/vi-VN.xml b/translation/dest/contact/vi-VN.xml index 29f857e9455fb..8fdf6392e587d 100644 --- a/translation/dest/contact/vi-VN.xml +++ b/translation/dest/contact/vi-VN.xml @@ -55,9 +55,7 @@ Trong một số trường hợp nhất định khi chơi với tài khoản Bot, ván cờ có xếp hạng có thể không trao điểm nếu xác định được rằng người chơi đang lạm dụng Bot để lấy điểm xếp hạng. Trang lỗi Nếu bạn gặp một trang lỗi, bạn có thể báo cáo nó: - Tôi muốn phát sóng một giải đấu Tìm hiểu cách thức phát sóng trên Lichess - Bạn cũng có thể liên hệ đội ngũ phát sóng về các chương trình phát sóng chính thức. Khiếu nại lệnh cấm, hạn chế hoặc chặn IP Đánh dấu gian lận hoặc dùng động cơ máy tính Bạn có thể gửi khiếu nại đến %s. diff --git a/translation/dest/contact/zh-CN.xml b/translation/dest/contact/zh-CN.xml index 7a18d0ea98b99..8fe9ae2ea0fbe 100644 --- a/translation/dest/contact/zh-CN.xml +++ b/translation/dest/contact/zh-CN.xml @@ -55,9 +55,7 @@ 在与机器人账号的对局中,若发现玩家滥用其以获取积分,那么这场对局将不会授予积分。 错误页面 如果你遇到错误页面,您可以举报: - 我想直播一个锦标赛 学习如何在 Lichess 创建直播 - 就官方直播相关事宜,你还可以与直播团队联系。 申请禁令或限制IP 引擎或作弊标记 你可以向 %s 发出申诉。 diff --git a/translation/dest/contact/zh-TW.xml b/translation/dest/contact/zh-TW.xml index 987ba11da7037..79deaff877094 100644 --- a/translation/dest/contact/zh-TW.xml +++ b/translation/dest/contact/zh-TW.xml @@ -55,9 +55,7 @@ 在與機器人帳號的對局中,若發現玩家濫用其以獲取積分,那麼這場對局將不會授予積分。 錯誤頁面 如果你遇到了錯誤頁面,你可以回報它: - 我想直播一個錦標賽 學習如何在 Lichess 創建直播 - 就官方直播相關事宜,你還可以與直播團隊聯繫。 懲處上訴 作弊或電腦操作記號 您可以將上訴寄到%s diff --git a/translation/dest/coordinates/bg-BG.xml b/translation/dest/coordinates/bg-BG.xml index b7bfc7ef62d61..395bbfa3d6b42 100644 --- a/translation/dest/coordinates/bg-BG.xml +++ b/translation/dest/coordinates/bg-BG.xml @@ -13,6 +13,7 @@ Имате 30 секунди да намерите колкото се може повече полета! Давайте до колкото си поискате, няма ограничение за време! Покажи координатите + Координати на всяко поле Покажи фигурите Започни упражнението Намери полето diff --git a/translation/dest/coordinates/el-GR.xml b/translation/dest/coordinates/el-GR.xml index e54ae270562ee..22ec4892dd5cb 100644 --- a/translation/dest/coordinates/el-GR.xml +++ b/translation/dest/coordinates/el-GR.xml @@ -13,6 +13,7 @@ Έχετε 30 δευτερόλεπτα για να διαλέξτε σωστά όσα τετράγωνα γίνεται! Πάρτε όσο χρόνο θέλετε, δεν υπάρχει χρονικό όριο! Εμφάνιση συντεταγμένων + Συντεταγμένες σε κάθε τετράγωνο Εμφάνιση κομματιών Έναρξη εξάσκησης Εύρεση τετραγώνου diff --git a/translation/dest/coordinates/tr-TR.xml b/translation/dest/coordinates/tr-TR.xml index 245d1aa591abc..93c4deaff8294 100644 --- a/translation/dest/coordinates/tr-TR.xml +++ b/translation/dest/coordinates/tr-TR.xml @@ -13,6 +13,7 @@ 30 saniye içinde kaç tane kare bulabilirsen! İstediğin kadar oyna, süre sınırı yok! Koordinatları göster + Her karede koordinatlar Taşları göster Antrenmana başla Kareleri bul diff --git a/translation/dest/coordinates/uk-UA.xml b/translation/dest/coordinates/uk-UA.xml index 2aa8a13ef823a..aa1b48441ed20 100644 --- a/translation/dest/coordinates/uk-UA.xml +++ b/translation/dest/coordinates/uk-UA.xml @@ -13,6 +13,7 @@ Ви маєте 30 секунд для того, щоб відмітити якнайбільше полів! Витрачайте стільки часу, скільки забажаєте - жодних лімітів! Відображати координати + Координати на кожному полі Показувати фігури Розпочати тренування Знайти поле diff --git a/translation/dest/dgt/he-IL.xml b/translation/dest/dgt/he-IL.xml index 42197b15671e2..bdc43cc689206 100644 --- a/translation/dest/dgt/he-IL.xml +++ b/translation/dest/dgt/he-IL.xml @@ -1,48 +1,48 @@ לוח DGT - ליצ׳ס ו-DGT + Lichess ו־DGT הדרישות עבור לוח DGT - ההגבלות של לוח ה-DGT - העמוד הזה מאפשר לך לחבר את לוח ה-DGT שברשותך ל-Lichess ולהשתמש בו כדי לשחק. - כדי לחבר את לוח ה-DGT האלקטרוני שלך יש להתקין את %s. + ההגבלות של לוח ה־DGT + העמוד הזה מאפשר לך לחבר את לוח ה־DGT שברשותך ל־Lichess ולהשתמש בו כדי לשחק. + כדי לחבר את לוח ה־DGT האלקטרוני שלך יש להתקין את %s. ניתן להוריד את התוכנה כאן: %s. אם %1$s מותקן על המחשב הזה, ניתן לבדוק את החיבור שלך אליו על ידי %2$s. פתיחת הקישור הזה - אם %1$s מותקן במכשיר אחר או בנמל (port) אחר, תצטרכו להגדיר את כתובת ה-IP והנמל פה ב%2$s. + אם %1$s מותקן במכשיר אחר או בנמל (port) אחר, תצטרכו להגדיר את כתובת ה־IP והנמל פה ב%2$s. הגדרות התצורה עמוד המשחק צריך להיות פתוח בדפדפן שלכם. הוא לא חייב להופיע על המסך – ניתן למשל למזער אותו. אולם אם תסגרו אותו, המשחק יפסיק לעבוד. הלוח יתחבר באופן אוטומטי לכל המשחקים בתהליך או לכל משחק חדש שתתחילו. בקרוב, יהיה ניתן לבחור אילו מבין המשחקים האלה לשחק באמצעות הלוח. קטגוריות הזמן למשחקים לא מדורגים: קצב קלאסי, מהיר ומשחקי התכתבות. - קטגוריות הזמן למשחקים מדורגים: התכתבות, קצב קלאסי וחלק מהקצבים המהירים, כולל 15+10 ו-20+0 + קטגוריות הזמן למשחקים מדורגים: התכתבות, קצב קלאסי וחלק מהקצבים המהירים, כולל 15+10 ו־20+0 כשאתם מוכנים, סדרו את הלוח שלהם ולחצו על %s. אם לא זוהה מהלך תחילה, בדקו שעשיתם את המהלך של היריב שלכם גם על הלוח שלהם. לאחר מכן, החזירו את המהלך שלכם ושחקו אותו שוב. - כמוצא אחרון, ערכו את הלוח בדיוק כפי שהוא מוצג בליצ׳ס, ואז %s + כמוצא אחרון, ערכו את הלוח בדיוק כפי שהוא מוצג ב־Lichess, ואז %s טענו את העמוד מחדש DGT - תצורה - החיבור לליצ׳ס + החיבור ל־Lichess יש לך תו OAuth שמתאים למשחק באמצעות לוח DGT. הערך ׳%s׳ התווסף לתפריט שלמעלה, תחת הקטגוריה ׳שחקו׳. לא נוצר תו OAuth מתאים. - החיבור ללוח ה-DGT + החיבור ללוח ה־DGT לחצו כדי ליצור אחד - הקישור ל-WebSocket של %s - השתמשו ב-%1$s אלא אם %2$s מופעל במכשיר אחר או בנמל (port) אחר. + הקישור ל־WebSocket של %s + השתמשו ב־%1$s אלא אם %2$s מופעל במכשיר אחר או בנמל (port) אחר. הקראת מהלכים כדי שתוכלו להתמקד בלוח, ניתן להקריא את המהלכים ששוחקו. הפעילו הקראה הקול של הקריין הכריזו על כל המהלכים - בחרו ב-YES כדי להקריא את כל המהלכים. בחרו ב-NO כדי להקריא רק את מהלכי יריבך. + בחרו ב־YES כדי להקריא את כל המהלכים. בחרו ב־NO כדי להקריא רק את מהלכי יריבך. פורמט הקראת המסעים - SAN זהו הסטנדרט בליצ׳ס: Nf6. + SAN זהו הסטנדרט ב־Lichess: Nf6. UCI נפוץ במנועי שחמט: g8f6. מילות מפתח מילות המפתח הן בפורמט JSON. הן משמשות כדי לתרגם את המהלכים והתוצאות לשפה שלך. ברירת המחדל היא אנגלית, אך ניתן לשנות אותה כרצונכם. פתרון תקלות Verbose logging - כדי לראות את ההודעות ב-Console לחצו Command + Option + C (Mac) or Control + Shift + C (Windows, Linux, Chrome OS) + כדי לראות את ההודעות ב־Console לחצו Command + Option + C (Mac) or Control + Shift + C (Windows, Linux, Chrome OS) שחקו עם לוח DGT הגדרות תצורה diff --git a/translation/dest/emails/he-IL.xml b/translation/dest/emails/he-IL.xml index e153c3075c180..19feea7bbe702 100644 --- a/translation/dest/emails/he-IL.xml +++ b/translation/dest/emails/he-IL.xml @@ -15,6 +15,6 @@ אנו מאחלים לכלים שלך תמיד למצוא את דרכם למלך של יריביך! %s, התחבר/י ל־lichess.org (אם לחיצה אינה עובדת, נסו הדבקה לתוך הדפדפן!) - זו הודעת שירות בקשר לשימושך ב-%s. - כדי ליצור איתנו קשר, השתמשו ב-%s. + זו הודעת שירות בקשר לשימושך ב־%s. + כדי ליצור איתנו קשר, השתמשו ב־%s. diff --git a/translation/dest/emails/uk-UA.xml b/translation/dest/emails/uk-UA.xml index 63683d9620763..da23be5613fb1 100644 --- a/translation/dest/emails/uk-UA.xml +++ b/translation/dest/emails/uk-UA.xml @@ -5,16 +5,16 @@ Якщо ви не реєструвалися на 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. diff --git a/translation/dest/faq/ca-ES.xml b/translation/dest/faq/ca-ES.xml index f5990aca3c54f..d1d6ee6c5ea36 100644 --- a/translation/dest/faq/ca-ES.xml +++ b/translation/dest/faq/ca-ES.xml @@ -167,7 +167,7 @@ Mostrem la icona vermella per a avisar-te quan això ocorre. Sovint pots permetr 3. Cliqueu Galetes i Permisos del lloc 4. Desplaceu-se cap avall i cliqueu Reproducció automàtica de contingut multimèdia 5. Afegiu lichess.org a Permet - Refrenar-me de jugar? + Refrenar-me de jugar? trastorn de salut mental independent aparences personalitzades de Lichess menys opcions de joc diff --git a/translation/dest/faq/ckb-IR.xml b/translation/dest/faq/ckb-IR.xml index ac11ec97edefb..b7bf6a7f62d82 100644 --- a/translation/dest/faq/ckb-IR.xml +++ b/translation/dest/faq/ckb-IR.xml @@ -140,5 +140,5 @@ کرتە لە ئایکۆنی قفڵەکە بکە کە لەتەنیشت ئەدرەسی lichess.org لە بەستەرەکەدایە لە وێبگەڕەکەت. دواتر دیاری بکە گەر دەتەوێ ئاگادارکردنەوەکانی لیچێست بۆ بێت یاخود نا. - خۆم لە یاریکردن بوەستێنم? + خۆم لە یاریکردن بوەستێنم? diff --git a/translation/dest/faq/da-DK.xml b/translation/dest/faq/da-DK.xml index d486c5260fb60..f1270ab4467e3 100644 --- a/translation/dest/faq/da-DK.xml +++ b/translation/dest/faq/da-DK.xml @@ -167,7 +167,7 @@ Vi viser det røde ikon for at advare dig, når dette sker. Ofte kan du udtrykke 3. Klik på Cookies og Website-tilladelser 4. Rul ned og klik på Media autoplay 5. Tilføj lichess.org til Tillad - Forhindre mig selv i at spille? + Forhindre mig selv i at spille? selvstændig psykisk lidelse Lichess-brugerstile færre lobby-puljer diff --git a/translation/dest/faq/el-GR.xml b/translation/dest/faq/el-GR.xml index c283799233072..2dc5b5b81f037 100644 --- a/translation/dest/faq/el-GR.xml +++ b/translation/dest/faq/el-GR.xml @@ -166,6 +166,6 @@ 3. Κάντε κλικ στην επιλογή Cookies και άδειες ιστοσελίδας 4. Κάντε κύλιση προς τα κάτω και κάντε κλικ στην αυτόματη αναπαραγωγή πολυμέσων 5. Προσθέστε το lichess.org στο επιτρέπω - Να σταματήσω τον εαυτό μου από το να παίζει; + Να σταματήσω τον εαυτό μου από το να παίζει; αυτόνομη κατάσταση ψυχικής υγείας diff --git a/translation/dest/faq/fa-IR.xml b/translation/dest/faq/fa-IR.xml index a300a82cc021b..77a9c5508767a 100644 --- a/translation/dest/faq/fa-IR.xml +++ b/translation/dest/faq/fa-IR.xml @@ -67,7 +67,7 @@ به طور کلی، نام های کاربری نباید: توهین آمیز، جعل هویت شخص دیگری یا تبلیغاتی باشند. می‌توانید درباره %1$s بیشتر بخوانید. دستورالعمل‌ها آیا می‌توانم نام کاربری خود را تغییر دهم؟ - خیر، نام کاربری به دلایل فنی و عملی قابل تغییر نیست. نام‌های کاربری در جاهای مختلف ثبت شده اند: پایگاه‌های داده، گزارش‌ها و در نظر مردم. می توانید یک بار حروف بزرگ را تنظیم کنید. + خیر، نام‌های کاربری به دلیل‌های فنی و عملی تغییرپذیر نیستند. نام‌های کاربری در جاهای زیادی ثبت شده‌اند: دادگان‌ها، برونبُردها، گزارش‌ها و در حافظه مردم. می‌توانید یک بار کوچکی-بزرگی حرف‌ها را تنظیم کنید. غنائم منحصر به فرد برای به دست آوردنش، hiimgosu خود را به چالش کشید تا تمام بازی های %s با استفاده از جنون برنده شود. یک مسابقه گلوله‌ای ساعتی diff --git a/translation/dest/faq/fi-FI.xml b/translation/dest/faq/fi-FI.xml index 8608c3ab2c1b2..c071e10a972bc 100644 --- a/translation/dest/faq/fi-FI.xml +++ b/translation/dest/faq/fi-FI.xml @@ -163,7 +163,7 @@ Tällöin sinulle ilmoitetaan asiasta näyttämällä punainen kuvake. Usein voi 3. Valitse Evästeet ja sivuston käyttöoikeudet 4. Vieritä alas ja valitse Median automaattinen toisto 4. Lisää lichess.org listalle \"Salli\" - Estää itseäni pelaamasta? + Estää itseäni pelaamasta? erilliseksi mielenterveyshäiriöksi Lichessin käyttäjäkohtaiset tyylit aulassa vähemmän pelivaihtoehtoja diff --git a/translation/dest/faq/gl-ES.xml b/translation/dest/faq/gl-ES.xml index 641a78bd2cbcb..a38d21920a71e 100644 --- a/translation/dest/faq/gl-ES.xml +++ b/translation/dest/faq/gl-ES.xml @@ -166,7 +166,7 @@ Amosamos a icona vermella para avisarte cando isto ocorre. A miúdo podes permit 3. Fai clic en Cookies e Permisos do Sitio 4. Vai cara abaixo e pulsa Reprodución automática 5. Engade lichess.org a Permitir - Deixar de xogar? + Deixar de xogar? enfermidade mental independente temas personalizados de Lichess menos emparellamentos rápidos diff --git a/translation/dest/faq/gsw-CH.xml b/translation/dest/faq/gsw-CH.xml index 206b2a52ef464..deb6f5e85adec 100644 --- a/translation/dest/faq/gsw-CH.xml +++ b/translation/dest/faq/gsw-CH.xml @@ -168,7 +168,7 @@ Das roti Symbol macht dich druf ufmerksam, dass das passiert. Oft chann mer lich 3. Klick \"Cookies and Site Permissions\" 4. Scroll abwärts und klick \"Media autoplay\" 5. \"Lichess.org\" zum Erlaube bi-füege - Mich sälber vum Schpille abhalte? + Mich sälber vum Schpille abhalte? eigene, geischtige G\'sundheitszueschtand Lichess Benutzerstil weniger Lobby-Pools diff --git a/translation/dest/faq/he-IL.xml b/translation/dest/faq/he-IL.xml index 3cb3afa0aed8e..0ac3111abef1c 100644 --- a/translation/dest/faq/he-IL.xml +++ b/translation/dest/faq/he-IL.xml @@ -17,10 +17,10 @@ האם ישנם אתרים המבוססים על ליצ\'ס? כן. ליצ\'ס אכן עורר השראה לאתרי קוד פתוח אחרים המשתמשים באתר שלנו %1$s, %2$s, או %3$s. באילו קיצורי מקלדת ניתן להשתמש? - בחלק מהעמודים בליצ׳ס ישנם קיצורי מקלדת שאפשר להשתמש בהם. לחצו על מקש ה-׳?׳ בלוח למידה, בלוח הניתוחים, בחידה או במשחק כדי לקבל רשימה של קיצורי המקלדת הזמינים. + בחלק מהעמודים ב־Lichessישנם קיצורי מקלדת שאפשר להשתמש בהם. לחצו על מקש ה־׳?׳ בלוח למידה, בלוח הניתוחים, בחידה או במשחק כדי לקבל רשימה של קיצורי המקלדת הזמינים. משחק הוגן מתי אני זכאי/ת להחזר דירוג אוטומטי מהפסד לרמאים? - דקה לאחר סימון שחקן/ית, 40 המשחקים המדורגים האחרונים מ-3 הימים האחרונים נבחנים. אם היית היריב/ה של השחקן/ית במשחקים הללו, איבדת דירוג (בגלל הפסד או תיקו) והדירוג שלך לא היה זמני, תקבל/י החזר דירוג. ההחזר מחושב על סמך דירוג השיא שלך והתקדמות הדירוג שלך לאחר המשחק. + דקה לאחר סימון שחקן/ית, 40 המשחקים המדורגים האחרונים מ־3 הימים האחרונים נבחנים. אם היית היריב/ה של השחקן/ית במשחקים הללו, איבדת דירוג (בגלל הפסד או תיקו) והדירוג שלך לא היה זמני, תקבל/י החזר דירוג. ההחזר מחושב על סמך דירוג השיא שלך והתקדמות הדירוג שלך לאחר המשחק. (לדוגמה, אם הדירוג שלך עלה מאוד לאחר המשחקים האלה, יתכן שלא תקבל/י החזר או רק החזר חלקי). ההחזר לעולם לא יעלה על 150 נקודות. מה נעשה לשחקנים שעוזבים משחקים מבלי להיכנע? אם יריבך מבטל משחקים או עוזב אותם לעיתים קרובות, הוא \"יושעה ממשחקים\", מה שאומר שיאסר עליו באופן זמני לשחק. זה לא יצוין באופן פומבי בפרופיל שלו. אם התנהגות זו תמשיך, אורך ההשעיה יעלה - והתנהגות ממושכת מסוג זה עלולה להוביל לסגירת החשבון שלו לצמיתות. @@ -41,7 +41,7 @@ ליצ\'ס תומך בשחמט רגיל וב%1$s. 8 סוגי שחמט מהו הפסד מאית החייל הממוצע (ACPL)? - הפסד מאית החייל הוא יחידת מידה המשמשת בשחמט כייצוג ליתרון. מאית חייל שווה ל-1/100 מחייל. לכן 100 מאיות חייל = חייל 1. ערכים אלה אינם ממלאים תפקיד רשמי במשחק אך הם מועילים לשחקנים, וחיוניים בשחמט מחשבים, לצורך הערכת עמדות. + הפסד מאית החייל הוא יחידת מידה המשמשת בשחמט כייצוג ליתרון. מאית חייל שווה ל־1/100 מחייל. לכן 100 מאיות חייל = חייל 1. ערכים אלה אינם ממלאים תפקיד רשמי במשחק אך הם מועילים לשחקנים, וחיוניים בשחמט מחשבים, לצורך הערכת עמדות. המהלך הממוחשב הטוב ביותר יאבד אפס מאיות חייל, אך מהלכים פחותים יביאו לעמדה נחותה אשר מיוצגת כאובדן מאיות חייל. ניתן להשתמש בערך זה כאינדיקטור לאיכות המשחק. ככל שפחות מאיות חייל מופסדות בכל מהלך, כך המשחק יותר מדויק. @@ -72,7 +72,7 @@ חזרה משולשת מתייחסת ל%1$s, לא תורות. החזרות אינן חייבות להתקיים ברצף. עמדות חזרנו על עמדה שלוש פעמים. מדוע המשחק לא נפסק בתיקו? - כדי להכריז על תיקו מפאת חזרה על עמדות, אחד השחקנים חייב לדרוש את זה. ניתן לעשות זאת על ידי לחיצה על הכפתור המוצג, או על ידי הצעת תיקו טרם ביצוע המהלך אשר יחזור על העמדה בפעם השלישית. דרישה לתיקו עקב כלל זה לא תלויה בהסכמת היריב. ניתן גם %1$s ליצ׳ס כדי לדרוש תיקו לאחר חזרה משולשת באופן אוטומטי. בנוסף, לאחר חזרה על אותה העמדה חמש פעמים המשחק יסתיים בתיקו בלי צורך בפעולה מצד אחד השחקנים. + כדי להכריז על תיקו מפאת חזרה על עמדות, אחד השחקנים חייב לדרוש את זה. ניתן לעשות זאת על ידי לחיצה על הכפתור המוצג, או על ידי הצעת תיקו טרם ביצוע המהלך אשר יחזור על העמדה בפעם השלישית. דרישה לתיקו עקב כלל זה לא תלויה בהסכמת היריב. ניתן גם %1$s Lichess כדי לדרוש תיקו לאחר חזרה משולשת באופן אוטומטי. בנוסף, לאחר חזרה על אותה העמדה חמש פעמים המשחק יסתיים בתיקו בלי צורך בפעולה מצד אחד השחקנים. לקבוע את תצורת חשבונות אילו תארים יש בLichess? @@ -81,14 +81,14 @@ אימות כשחקן/ית עם תואר בלִיצֶ\'ס נותן גישה לטורנירי זירה לשחקנים כאלה (Titled Arenas). -יש בליצ׳ס גם את תואר הכבוד %2$s. +יש ב־Lichessגם את תואר הכבוד %2$s. תארי אמן של מדינות רבות טופס האימות - האם אני יכול/ה לקבל את דרגת ״אמן ליצ׳ס״ (LM)? + האם אני יכול/ה לקבל את דרגת ״אמן Lichess״ (LM)? לא. - הדרגה הלא רשמית הזו היא לשם כבוד וקיימת ב-Lichess בלבד. + הדרגה הלא רשמית הזו היא לשם כבוד וקיימת ב־Lichess בלבד. -אנו מעניקים אותה לשחקנים ראויים לציון שמפגינים אזרחות טובה ב-Lichess, על פי שיקול דעתנו. אם את/ה זכאי/ת לדרגה זו, תקבל/י מאיתנו הודעה בנידון עם אפשרות לאשר או לדחות את ההצעה. +אנו מעניקים אותה לשחקנים ראויים לציון שמפגינים אזרחות טובה ב־Lichess, על פי שיקול דעתנו. אם את/ה זכאי/ת לדרגה זו, תקבל/י מאיתנו הודעה בנידון עם אפשרות לאשר או לדחות את ההצעה. נא לא לבקש את דרגת LM. מה יכול להיות שם המשתמש שלי? @@ -97,7 +97,7 @@ האם אני יכול/ה לשנות את שם המשתמש שלי? לא, לא ניתן לשנות את שם המשתמש מסיבות פרקטיות וטכניות. שמות משתמש מופיעים במקומות רבים כגון מסדי נתונים, דוחות ומסמכים מיוצאים. הם גם צרובים במוחם של אנשים. ניתן להחליף בין אותיות גדולות ואותיות קטנות פעם אחת. גביעים ייחודיים - הגביע הזה הוא ייחודי בהיסטוריה של ליצ׳ס, אף אחד מלבד %1$s לא יזכה בו. + הגביע הזה הוא ייחודי בהיסטוריה של Lichess, אף אחד מלבד %1$s לא יזכה בו. כדי להשיג אותו, הימגוסו אתגר את עצמו להיכנס ל\'אטרף\' ולנצח בכל המשחקים של %s. טורניר הBullet השעתי ZugAddict שידר במשך שעתיים וניסה ללא הצלחה להביס מחשב ברמה 8 במשחק 1+0. Thibault אמר שאם יצליח לעשות זאת בזמן המשדר, הוא יקבל גביע ייחודי. לאחר שעה, הוא הביס את Stockfish וקוימה ההבטחה. @@ -105,7 +105,7 @@ באיזו מערכת דירוג Lichess משתמש? הדירוג מחושב באמצעות שיטת Glicko-2 שפותחה על ידי מארק גליקמן. זו שיטת דירוג מאוד פופולארית שגופי שחמט רבים עושים בה שימוש (FIDE היא יוצאת מן הכלל ומשתמשת בשיטת Elo). -שיטות גליקו מסתמכות על מקדמי ודאות. כששחקן נרשם לליצ׳ס, דירוגו יכול לנוע בין 500 ל־2500 ברמת ודאות של 95 אחוזים, כאשר הדירוג ההתחלתי הוא 1500. בתחילה מקדם הוודאות הוא נמוך, ולכן משחק בודד יכול לשנות את הדירוג אף במאות נקודות. אולם ככל שנצברים משחקים הוודאות עולה והשינוי ממשחק למשחק קטן. +שיטות גליקו מסתמכות על מקדמי ודאות. כששחקן נרשם ל־Lichess, דירוגו יכול לנוע בין 500 ל־2500 ברמת ודאות של 95 אחוזים, כאשר הדירוג ההתחלתי הוא 1500. בתחילה מקדם הוודאות הוא נמוך, ולכן משחק בודד יכול לשנות את הדירוג אף במאות נקודות. אולם ככל שנצברים משחקים הוודאות עולה והשינוי ממשחק למשחק קטן. נקודה נוספת הדורשת הבהרה היא שככל שעובר זמן מבלי שהשחקן היה פעיל רמת הוודאות קטנה ולכן שוב יגדל השינוי בדירוג ממשחק למשחק. זאת, כדי שהדירוג יהיה מותאם לשינוי ביכולתו של השחקן לאורך זמן. מדוע מופיע סימן שאלה (?) ליד הדירוג שלי? @@ -113,16 +113,16 @@ השחקן לא השלים מספיק משחקים מדורגים נגד %1$s בקטגוריית הדירוג. יריבים ברמה דומה השחקן לא שיחק מספיק משחקים בזמן האחרון. כתלות במספר המשחקים ששיחקת, יתכן שכעבור שנה של חוסר פעילות הדירוג יהפוך שוב לזמני. - באופן קונקרטי, הדירוג זמני אם הסטייה בו גדולה מ-110. זוהי רמת הוודאות שלנו באשר לדירוגך. כלל שהסטייה יותר נמוכה, דירוגך יציב יותר. + באופן קונקרטי, הדירוג זמני אם הסטייה בו גדולה מ־110. זוהי רמת הוודאות שלנו באשר לדירוגך. כלל שהסטייה יותר נמוכה, דירוגך יציב יותר. איך דירוג וטבלאות דירוג עובדים? כדי להעפיל ל%1$s עליך: טבלת הדירוג לשחק לפחות 30 משחקים מדורגים בקטגוריית הדירוג לשחק בקטגוריית דירוג זו לפחות משחק מדורג אחד בשבוע האחרון - להיות בעל סטייה בדירוג הנמוכה מ-%1$s בשח סטנדרטי ונמוכה מ-%2$s בגרסאות אחרות + להיות בעל סטייה בדירוג הנמוכה מ־%1$s בשח סטנדרטי ונמוכה מ־%2$s בגרסאות אחרות להיות בעשירייה המובילה לדירוג זה. הדרישה השנייה היא כדי שרק שחקנים פעילים יופיעו בטבלאות האלופים. - מדוע הדירוגים באתר גבוהים בהשוואה לארגונים ואתרים אחרים כגון FIDE, USCF ו-ICC? + מדוע הדירוגים באתר גבוהים בהשוואה לארגונים ואתרים אחרים כגון FIDE, USCF ו־ICC? אין זה נכון לחשוב על דירוג כמספר מוחלט או להשוות אותו למקביליו בארגונים אחרים. לארגונים שונים שחקנים ברמות שונות ושיטות שונות לקביעת הדירוג (Elo, Glicko-2, Glicko או גרסאות שלהן). לגורמים האלה השפעה מכרעת על הדירוג. עדיף לחשוב על דירוג כמונח יחסי. לכל אוכלוסיית שחקנים בארגון או אתר מסוים, הדירוג ינבא מי ינצח, יפסיד או ישיג תיקו, ובאיזו תדירות. להגיד: ״הדירוג שלי הוא X״ זה חסר משמעות כל עוד אין שחקנים אחרים אליהם הדירוג מושווה. @@ -130,7 +130,7 @@ הפעילו את מצב זן ב%1$s או על ידי הקשה על %2$s במהלך משחק. הגדרות התצוגה הפסדתי משחק עקב בעיות תקשרות/ניתוק. האם ניתן לקבל את נקודות הדירוג שלי בחזרה? - לצערנו, איננו יכולים להחזיר נקודות דירוג שאבדו עקב תקלת רשת או ניתוק, בין אם היא אצלך או אצלנו (על אף שאצלנו מתרחשות תקלות כאלה רק לעיתים נדירות). כאשר מבוצעת הפעלה מחדש של ליצ׳ס וכתוצאה מכך נגמר לך הזמן, אנו מבטלים את המשחק כדי למנוע הפסד לא הוגן. + לצערנו, איננו יכולים להחזיר נקודות דירוג שאבדו עקב תקלת רשת או ניתוק, בין אם היא אצלך או אצלנו (על אף שאצלנו מתרחשות תקלות כאלה רק לעיתים נדירות). כאשר מבוצעת הפעלה מחדש של Lichess וכתוצאה מכך נגמר לך הזמן, אנו מבטלים את המשחק כדי למנוע הפסד לא הוגן. כיצד... להפעיל או לחסום התראות קופצות? צפה בחלון הקופץ המכיל מידע על האתר @@ -142,17 +142,17 @@ להפעיל ניגון אוטומטי של צלילים? רוב דפדפני האינטרנט לא מאפשרים ניגון צלילים בעמודים חדשים כדי להגן על המשתמשים. כך, לא כל אתר קיקיוני יכול להפציץ אותנו בפרסומות קוליות. בדרך כלל ההגבלה הזאת מבוטלת ברגע שאנו לוחצים על משהו בתוך האתר. בחלק מהדפדפנים, הזזת הכלים בלוח לא נחשבת לחיצה, ואז יהיה עליהם ללחוץ במקום ריק על הלוח כדי לאפשר הפעלת צלילים. תאלצו לעשות זאת כל פעם שאתם מתחילים משחק. -אנו מציגים סמליל אדום כדי להודיע לכם כשזה קורה. ברוב המקרים, ניתן לאפשר לדפדפן להפעיל צלילים ב-lichess.org כברירת מחדל ובכך לחסוך לחיצות מיותרות על הלוח. עקבו אחר ההוראות הבאות על פי הדפדפן שלכם. +אנו מציגים סמליל אדום כדי להודיע לכם כשזה קורה. ברוב המקרים, ניתן לאפשר לדפדפן להפעיל צלילים ב־lichess.org כברירת מחדל ובכך לחסוך לחיצות מיותרות על הלוח. עקבו אחר ההוראות הבאות על פי הדפדפן שלכם. מחשבים - 1. היכנסו ל-lichess.org -2. לחצו על Ctrl-i ב-Linux או Windows או על cmd ב-MacOS + 1. היכנסו ל־lichess.org +2. לחצו על Ctrl-i ב־Linux או Windows או על cmd ב־MacOS 3. לחצו על הלשונית ״הרשאות״ (Permissions) -4. אפשרו הפעלת צלילים (Audio) וסרטונים (Video) ב-lichess.org - 1. היכנסו ל-lichess.org +4. אפשרו הפעלת צלילים (Audio) וסרטונים (Video) ב־lichess.org + 1. היכנסו ל־lichess.org 2. לחצו על סמליל המנעול בשורת החיפוש 3. לחצו על ״הגדרות האתר״ 4. אפשרו הפעלת צלילים - 1. היכנסו ל-lichess.org + 1. היכנסו ל־lichess.org 2. לחצו על Safari בתפריט החיפוש 3. לחצו על ״הגדרות עבור lichess.org״ 4. אפשרו הפעלה אוטומטית באופן גורף @@ -161,13 +161,13 @@ 3. לחצו על ״עוגיות והרשאות״ 4. גללו למטה ולחצו על ״הפעלה אוטומטית של מדיה״ 5. הוסיפו את lichess.org לרשימת האתרים המורשים - למנוע מעצמי להמשיך לשחק? + למנוע מעצמי להמשיך לשחק? מצב נפשי - האפשרות של תוספים חיצוניים ל-Lichess + האפשרות של תוספים חיצוניים ל־Lichess פחות אפשרויות משחק בלובי לעיתים קרובות אנו מקבלים הודעות ממשתמשים שמבקשים שנעזור להם להפחית את תדירות המשחק שלהם. -איננו חוסמים משתמשים למעט במצבים של הפרת תנאי השימוש. אנו ממליצים על התקנים חיצוניים, לרבות %1$s, %2$s ו-%3$s. אם אתם רוצים להמשיך לשחק אך לא להתפתות ליצור משחקים קצרים, בדקו את %4$s. הנה אחד עם %5$s. +איננו חוסמים משתמשים למעט במצבים של הפרת תנאי השימוש. אנו ממליצים על התקנים חיצוניים, לרבות %1$s, %2$s ו־%3$s. אם אתם רוצים להמשיך לשחק אך לא להתפתות ליצור משחקים קצרים, בדקו את %4$s. הנה אחד עם %5$s. חלק מהשחקנים מרגישים שהם מתמכרים למשחק. למעשה, ארגון הבריאות העולמי מגדיר התמכרות למשחק כ%6$s, המתאפיין בחוסר שליטה על תדירות המשחק ופיתוח של תלות בו על אף השפעותיו השליליות. אם אתם מזהים את הדפוס הזה אצלכם, אנו ממליצים להיוועץ בבן משפחה או באיש מקצוע. diff --git a/translation/dest/faq/hu-HU.xml b/translation/dest/faq/hu-HU.xml index 779b846f73e88..f21faa85f1911 100644 --- a/translation/dest/faq/hu-HU.xml +++ b/translation/dest/faq/hu-HU.xml @@ -144,4 +144,31 @@ Kattints a lakat ikonra a böngésző címsorában, a lichess.org mellett. Utána válaszd ki, hogy engedélyezed vagy tiltod az Lichess értesítéseit. Engedélyezed hangok automatikus lejátszását? + A legtöbb böngésző a felhasználók védelme érdekében megakadályozhatja a hang lejátszását a frissen betöltött oldalakon. Képzeld el, milyen lenne, ha minden weboldal azonnal hanghirdetésekkel bombázhatna... + +A piros némító ikon akkor jelenik meg, ha a böngésző megakadályozta a lichess.org hangjainak lejátszását. Általában ez a korlátozás feloldódik, amint rákattintasz valamire. Egyes mobilböngészőkben egy bábu érintéssel történő húzása nem számít kattintásnak. Ebben az esetben minden játék kezdetén meg kell érintened a táblát, hogy engedélyezd a hangok lejátszását. + +A piros ikont azért mutatjuk, hogy figyelmeztessünk, amikor ez történik. Sok esetben lehetséges külön engedélyezni a lichess.org számára a hangok lejátszását. Itt találod az erre vonatkozó utasításokat néhány népszerű böngésző legújabb verziójához. + asztali + 1. Nyisd meg a lichess.org weboldalt +2. Használd a Ctrl-i billentyűkombinációt Linuxon és Windowson vagy a cmd-i billentyűkombinációt MacOS-en +3. Nyisd meg az Engedélyek oldalt +4. Engedélyezd a videó- és hanglejátszást a lichess. org oldalon + 1. Nyisd meg a lichess.org weboldalt +2. Kattints a lakat ikonra a címsor mellett +3. Kattints az Oldalbeállításokra +4. Engedélyezd a hangok lejátszását + 1. Nyisd meg a lichess.org weboldalt +2. Kattints a Safari-ra a menüsorban +3. Nyisd meg a lichess.org weboldalra vonatkozó beállításokat +4. Engedélyezd a mindenre vonatkozó automatikus lejátszást + 1. Kattints a három pontra a jobb felső sarokban a menü megnyitásához +2. Nyisd meg a Beállításokat +3. Nyisd meg a Sütik és oldalengedélyek menüpontot +4. Görgess lejjebb és nyisd meg a Média automatikus lejátszása menüpontot +5. Add hozzá a lichess.org weboldalt az engedélyezéshez + Hagyjam abba a játékot? + mentális betegség + Lichess egyéni stílusok + kevesebb lobbival diff --git a/translation/dest/faq/it-IT.xml b/translation/dest/faq/it-IT.xml index 79961de8653d9..25bc1ee539b00 100644 --- a/translation/dest/faq/it-IT.xml +++ b/translation/dest/faq/it-IT.xml @@ -167,6 +167,6 @@ Ti mostriamo l\'icona rossa di muto per avvisarti quando questo succede. Spesso 3. Clicca su Cookies e autorizzazioni del sito 4. Scendi nella pagina e clicca su Riproduzione automatica di contenuti multimediali 5. Aggiungi lichess.org agli autorizzati - Smettere di giocare? + Smettere di giocare? Stili utente di lichess diff --git a/translation/dest/faq/lb-LU.xml b/translation/dest/faq/lb-LU.xml index c963825bb3f42..370c21362ebb6 100644 --- a/translation/dest/faq/lb-LU.xml +++ b/translation/dest/faq/lb-LU.xml @@ -73,6 +73,6 @@ Wéi... Notifikatiouns-Popups aktivéieren an desaktivéieren? Desktop - Mech selwer vum s`Spillen ofhalen? + Mech selwer vum s`Spillen ofhalen? Lichess-Benotzerstiler diff --git a/translation/dest/faq/nn-NO.xml b/translation/dest/faq/nn-NO.xml index 332c39e471fbe..00d5799f46d45 100644 --- a/translation/dest/faq/nn-NO.xml +++ b/translation/dest/faq/nn-NO.xml @@ -167,7 +167,7 @@ Vi viser det raude ikonet for å gje ei åtvaring når dette skjer. Ofte kan du 3. Klikk informasjonskapslar og rettar 4. Rull ned og klikk Media autospel 5. Legg til lichess.org for å tillate - Stoppa meg frå å spela? + Stoppa meg frå å spela? eigen mental tilstand Lichess brukarstilar færre lobbysamlingar diff --git a/translation/dest/faq/ru-RU.xml b/translation/dest/faq/ru-RU.xml index f578d68749329..9c39de0323734 100644 --- a/translation/dest/faq/ru-RU.xml +++ b/translation/dest/faq/ru-RU.xml @@ -167,7 +167,7 @@ 3. Щелкните Cookies и разрешения сайта 4. Прокрутите вниз и щёлкните автовоспроизведение Media 5. Добавьте lichess.org для разрешения - Как воздерживаться от игр? + Как воздерживаться от игр? отдельное психическое расстройство пользовательские CSS-стили Lichess меньшее количество вариантов в Зале ожидания diff --git a/translation/dest/faq/sk-SK.xml b/translation/dest/faq/sk-SK.xml index f326fc3dea041..92ad5b4cdc0d4 100644 --- a/translation/dest/faq/sk-SK.xml +++ b/translation/dest/faq/sk-SK.xml @@ -145,7 +145,7 @@ Najlepšie je považovať rating za „relatívne“ čísla (na rozdiel od „a Kliknite na ikonu zámku vedľa adresy lichess.org v paneli URL vášho prehliadača. Potom vyberte, či chcete povoliť alebo blokovať upozornenia od Lichess. - Ako mám prestať hrať? + Ako mám prestať hrať? samostatný stav duševného zdravia Používateľské štýly Lichess menej prednastavených partií na domovskej stránke diff --git a/translation/dest/faq/sq-AL.xml b/translation/dest/faq/sq-AL.xml index 739749c21ee5a..1f46a897a69d2 100644 --- a/translation/dest/faq/sq-AL.xml +++ b/translation/dest/faq/sq-AL.xml @@ -167,7 +167,7 @@ Ikonën e kuqe jua shfaqim për t’ju sinjalizuar, kur ndodh kjo. Shpesh, mund 3. Klikoni Leje Cookie-sh dhe Sajti 4. Shkoni më poshtë dhe klikoni “Vetëluajtje mediash” 5. Shtoni lichess.org te Lejoje - Të ndal veten nga loja? + Të ndal veten nga loja? Marrim rregullisht mesazhe nga përdorues ku na kërkohet t’i ndihmojmë të reshtin së luajturi tej mase. Teksa Lichess-i nuk dëbon, apo bllokon lojtarë, hiq cenime të Kushteve të Shërbimit, rekomandojmë përdorim mjetesh të jashtme për kufizim sjelljeje të tepruar të luajturi. Disa nga sugjerimet e rëndomta për bllokues sajtesh përfshijnë %1$s, %2$s dhe %3$s. Nësedoni të vazhdoni të përdorni sajtin, por pa qenë të joshur nga kontrolle të shpejtë kohe, mund t’ju interesojnë edhe %4$s, ja një me %5$s. diff --git a/translation/dest/insight/es-ES.xml b/translation/dest/insight/es-ES.xml index d678781bc1e3a..61f14dbe68159 100644 --- a/translation/dest/insight/es-ES.xml +++ b/translation/dest/insight/es-ES.xml @@ -5,7 +5,7 @@ Las valoraciones de %s están protegidas Lo sentimos, no puedes ver las valoraciones de %s. Generar valoraciones para %s. - ¡Procesando datos solo para ti! + ¡Procesando datos sólo para ti! ¿Tal vez solicitar que cambien su %s? ajustes de valoraciones diff --git a/translation/dest/keyboardMove/he-IL.xml b/translation/dest/keyboardMove/he-IL.xml index d828c0265ee68..8ea62d5e189f2 100644 --- a/translation/dest/keyboardMove/he-IL.xml +++ b/translation/dest/keyboardMove/he-IL.xml @@ -2,8 +2,8 @@ פקודות קלט מקלדת ביצוע מהלך - הזזת כלי מ-e2 ל-e4 - הזזת פרש ל-c3 + הזזת כלי מ־e2 ל־e4 + הזזת פרש ל־c3 הצרחה קטנה הצרחה גדולה הכתרת רגלי c8 למלכה diff --git a/translation/dest/lag/he-IL.xml b/translation/dest/lag/he-IL.xml index 2c27d3cca6962..de0abc8658181 100644 --- a/translation/dest/lag/he-IL.xml +++ b/translation/dest/lag/he-IL.xml @@ -1,12 +1,12 @@ - האם יש לאג בליצ׳ס? + האם יש לאג ב־Lichess? מדידה מתבצעת... לא. והרשת שלך עובדת סבבה. לא. והרשת שלך לא עובדת כראוי. כן. זה יתוקן בקרוב! ועכשיו, התשובה הארוכה! לאג במשחק נגרם משני ערכים לא קשורים אחד לשני (ככל שהם נמוכים יותר כך יותר טוב): - זמן התגובה של שרתי ליצ׳ס + זמן התגובה של שרתי Lichess הזמן שלוקח למהלך להתעדכן בשרת. הוא זהה לכולם, ורק תלוי בשרתים שלנו. הוא נהיה יותר ארוך ככל שיש יותר שחקנים, אבל המפתחים שלנו עושים את המיטב כדי לגרום לו להיות נמוך כמה שיותר. הוא בדרך כלל נשאר מתחת לעשירית שנייה. הרשת שבין ליצ\'ס למכשיר שלך הזמן שלוקח למכשיר שלך לשלוח את המהלך ללִיצֶ\'ס ולקבל את התשובה בחזרה. הוא תלוי במרחק שלך לצרפת (שם נמצאים השרתים שלנו) ובחיבור שלך לאינטרנט. מפתחי לִיצֶ\'ס לא יכולים לתקן לך את האינטרנט, ולכן לא יכולים לגרום לפרמטר זה להיות יותר מהיר. diff --git a/translation/dest/learn/he-IL.xml b/translation/dest/learn/he-IL.xml index 9038e8b792fae..bd03587edfa3a 100644 --- a/translation/dest/learn/he-IL.xml +++ b/translation/dest/learn/he-IL.xml @@ -14,7 +14,7 @@ הקישו על הצריח כדי להזיזו לכוכב! אספו את כל הכוכבים! ככל שתמעט/י במסעים, תרבה/י בנקודות! - השתמש/י ב-2 צריחים + השתמש/י ב־2 צריחים כדי לזרז את העניינים! יפה, את/ה שולט/ת בצריחים. הרץ diff --git a/translation/dest/oauthScope/he-IL.xml b/translation/dest/oauthScope/he-IL.xml index 6b0052c54e280..5940ae2ba238d 100644 --- a/translation/dest/oauthScope/he-IL.xml +++ b/translation/dest/oauthScope/he-IL.xml @@ -1,6 +1,6 @@ - תו גישה אישי חדש ל-API + תו גישה אישי חדש ל־API התו נותן לאנשים אחרים גישה להשתמש בחשבונך. בחרו בזהירות מה ניתן לעשות בשמכם בחשבונכם. תיאור התו @@ -31,7 +31,7 @@ יצירה ועדכון של מנוע חיצוני יצירה של התחברות מאובטחת לאתרים (עם גישה מלאה)! שימוש בכלים של מנהלים (במסגרת ההרשאות שלך) - תווי גישה אישיים ל-API + תווי גישה אישיים ל־API ניתן לבקש בקשות OAuth מבלי לעבור %s. authorization code flow לחלופין, %s שתוכלו להשתמש בו ישירות לצורך בקשות API. @@ -39,16 +39,16 @@ שמרו על התווים האלה בזהירות רבה! הם כמו סיסמאות. היתרון בשימוש בתווים על פני סיסמאות הוא שניתן לבטל אותם או לייצר רבים מהם. הנה %1$s ופה ניתן למצוא %2$s. דוגמה ליישומון של תווים אישיים - תיעוד ה-API + תיעוד ה־API תו גישה חדש - תווי גישה ל-API + תווי גישה ל־API נוצר %s היה בשימוש לאחרונה %s כבר שיחקת משחקים! הערה לשימוש מתכנתים בלבד: - ניתן למלא מראש את הטופס הזה על ידי שינויי ה-query parameters של הקישור (URL). + ניתן למלא מראש את הטופס הזה על ידי שינויי ה־query parameters של הקישור (URL). לדוגמה: %s מגדיר את ההיקף (scope) של %1$s ושל %2$s ויוצר תיאור לתו הגישה. - ניתן למצוא את קטעי הקוד של הגדרות ההיקף (scope codes) בקוד ה-HTML של הטופס. + ניתן למצוא את קטעי הקוד של הגדרות ההיקף (scope codes) בקוד ה־HTML של הטופס. תוכלו לתת למשתמשים שלכם את הקישורים האלה שמולאו מראש כדי לעזור להם לקבל את התווים בהיקף המתאים. diff --git a/translation/dest/onboarding/bg-BG.xml b/translation/dest/onboarding/bg-BG.xml index 3ea04e700dfa8..b734e9dde265e 100644 --- a/translation/dest/onboarding/bg-BG.xml +++ b/translation/dest/onboarding/bg-BG.xml @@ -1,2 +1,10 @@ - + + Добре дошли! + Добре дошли в lichess.org! + Това е страницата на Вашия профил. + А сега какво? Ето няколко предложения: + Научете правилата на шахмата + Участвайте в турнири. + Учете се с помощта на %1$s и %2$s. + diff --git a/translation/dest/onboarding/de-DE.xml b/translation/dest/onboarding/de-DE.xml index a903f81ba50a5..3245c3c8ff04d 100644 --- a/translation/dest/onboarding/de-DE.xml +++ b/translation/dest/onboarding/de-DE.xml @@ -6,7 +6,7 @@ Wird ein Kind dieses Konto verwenden? Vielleicht möchtest du den %s aktivieren. Was nun? Hier sind ein paar Vorschläge: Schachregeln lernen - Verbessere dein Schach mit Taktikaufgaben. + Verbessere dein Schach mit Taktik-Aufgaben. Spiele gegen die künstliche Intelligenz. Spiele gegen Gegner aus der ganzen Welt. Folge deinen Freunden auf Lichess. diff --git a/translation/dest/onboarding/fa-IR.xml b/translation/dest/onboarding/fa-IR.xml index 28e40a9bc6d0f..002690c486ab5 100644 --- a/translation/dest/onboarding/fa-IR.xml +++ b/translation/dest/onboarding/fa-IR.xml @@ -2,7 +2,7 @@ خوش آمدید! به لیچس خوش آمدید! - این صفحه نمایه‌تان است. + این صفحه رُخ‌نمای‌تان است. آیا یک کودک قرار است از این حساب استفاده کند؟ شاید بخواهید %s را فعال کنید. حالا چی؟ این‌ها پیشنهاد ماست: قوانین شطرنج را یاد بگیرید diff --git a/translation/dest/onboarding/he-IL.xml b/translation/dest/onboarding/he-IL.xml index 4c7d4b381b487..671684fcad327 100644 --- a/translation/dest/onboarding/he-IL.xml +++ b/translation/dest/onboarding/he-IL.xml @@ -1,7 +1,7 @@ ברוכים הבאים! - ברוכים הבאים ל-lichess.org! + ברוכים הבאים ל־lichess.org! זה עמוד הפרופיל שלך. אם החשבון הזה מיועד לילד/ה, מומלץ להפעיל את %s. מה עכשיו? הנה כמה הצעות: @@ -9,9 +9,9 @@ השתפרו על ידי פתירת חידות שחמט. שחקו נגד בינה מלאכותית. שחקו נגד יריבים מרחבי העולם. - עקבו אחר חבריכם בליצ׳ס. + עקבו אחר חבריכם ב־Lichess. שחקו בטורנירים. למדו מ%1$s ומ%2$s. - התאימו את ליצ׳ס להעדפותיכם. + התאימו את Lichess להעדפותיכם. שוטטו ברחבי האתר ותהנו :) diff --git a/translation/dest/patron/fa-IR.xml b/translation/dest/patron/fa-IR.xml index 73ca6bf6362f3..377cdd521f152 100644 --- a/translation/dest/patron/fa-IR.xml +++ b/translation/dest/patron/fa-IR.xml @@ -48,7 +48,7 @@ لطفاً توجه داشته باشید که فقط برگه کمک مالی بالا، جایگاه حامی را به ارمغان می‌آورد. آیا برخی ویژگیها فقط برای پشتیبان‌ها قابل دسترس است؟ نه، زیرا Lichess برای همیشه و برای همه، کاملا رایگان است. قول می‌دهیم. -با این حال، حامیان با بال‌های معرکه‌ای که در نمایه‌شان نشان داده می‌شود، حق پُز دادن دارند. +با این حال، حامیان با بال‌های معرکه‌ای که در رُخ‌نمای‌شان نشان داده می‌شود، حق پُز دادن دارند. مقایسه جزئیاتِ ویژگیها را مشاهده کنید پشتیبانِ Lichess برای یک ماه @@ -75,7 +75,7 @@ مقدار تراکنشِ شما تکمیل شد، و یک رسید برای کمک مالی شما برایتان ایمیل شد. شما هم‌اکنون یک حساب دائمی پشتیبان دارید. - به صفحه نمایه‌تان سَر بزنید! + به صفحه رُخ‌نمای‌تان سَر بزنید! شما هم‌اکنون یک پشتیبانِ مادام‌العمرِ Lichess هستید! شما هم‌اکنون برای یک ماه یک پشتیبانِ Lichess هستید! طی یک ماه، دوباره برای شما بدهکاری ثبت نخواهد شد، و حساب کاربری Lichess شما به یک حساب کاربری معمولی برگردانده خواهد شد. diff --git a/translation/dest/patron/he-IL.xml b/translation/dest/patron/he-IL.xml index 0c6ff10760da2..e4b806e55a786 100644 --- a/translation/dest/patron/he-IL.xml +++ b/translation/dest/patron/he-IL.xml @@ -36,7 +36,7 @@ התומכים המהוללים שהופכים את לִיצֶ\'ס לאפשרי לאן הכסף הולך? בראש ובראשונה, לשרתים חזקים. -לאחר מכן אנחנו משלמים למפתח במשרה מלאה: %s, המייסד של ליצ׳ס. +לאחר מכן אנחנו משלמים למפתח במשרה מלאה: %s, המייסד של Lichess. צפו במפרט ההוצאות הכלכליות האם לִיצֶ\'ס הוא ארגון ללא מטרות רווח באופן רשמי? כן, הנה מסמך היצירה (בצרפתית) @@ -45,7 +45,7 @@ או שאפשר %s. ליצור קשר עם התמיכה שלנו האם יש אמצעים אחרים לתרום? - ליצ׳ס רשום ל-%s. + Lichess רשום ל־%s. אנחנו מקבלים גם העברות בנקאיות לידיעתכם: רק טופס התרומה שלעיל מקנה את כנפי התורם/ת. האם יש יתרונות ששמורים רק עבור תומכים? diff --git a/translation/dest/perfStat/he-IL.xml b/translation/dest/perfStat/he-IL.xml index 5c37ed7c60906..a5cf9b1c1c119 100644 --- a/translation/dest/perfStat/he-IL.xml +++ b/translation/dest/perfStat/he-IL.xml @@ -6,7 +6,7 @@ לא ניתן לקבוע דירוג אמין, מפני שלא שוחקו מספיק משחקים מדורגים. התקדמות לאורך %s המשחקים האחרונים: סטיית תקן בדירוג: %s. - ערך נמוך יותר אומר שהדירוג יציב יותר. מעל %1$s הדירוג נחשב זמני. כדי להיכלל במדרג השחקנים, ערך זה צריך להיות מתחת ל-%2$s (שחמט רגיל) או %3$s (וריאנטים). + ערך נמוך יותר אומר שהדירוג יציב יותר. מעל %1$s הדירוג נחשב זמני. כדי להיכלל במדרג השחקנים, ערך זה צריך להיות מתחת ל־%2$s (שחמט רגיל) או %3$s (וריאנטים). סך כל המשחקים משחקים מדורגים משחקי טורניר diff --git a/translation/dest/perfStat/uk-UA.xml b/translation/dest/perfStat/uk-UA.xml index 4bdfdf6053bdd..ebf84c15bbc9b 100644 --- a/translation/dest/perfStat/uk-UA.xml +++ b/translation/dest/perfStat/uk-UA.xml @@ -10,7 +10,7 @@ Всього ігор Рейтингові ігри Турнірні ігри - Ігри з берсерком + Партії з берсерком Проведено часу у грі Середній рейтинг суперників Перемог diff --git a/translation/dest/preferences/af-ZA.xml b/translation/dest/preferences/af-ZA.xml index d6341b22921f3..096e34e2f04b5 100644 --- a/translation/dest/preferences/af-ZA.xml +++ b/translation/dest/preferences/af-ZA.xml @@ -48,7 +48,7 @@ Maak skuiwe met jou stem Beperk pyltjies tot geldige skuiwe Sê \"Good game, well played\" (Goeie spel, mooi gespeel) met \'n nederlaag of gelykop - Jou voorkeure is gestoor. + Jou voorkeure is gestoor. Roll die muis op die bord om skuiwe te herspeel Daaglikse e-pos met \'n lys van jou korrespondensiespelle Uitsending het begin diff --git a/translation/dest/preferences/an-ES.xml b/translation/dest/preferences/an-ES.xml index 5b13f7a482407..9a3320e239d43 100644 --- a/translation/dest/preferences/an-ES.xml +++ b/translation/dest/preferences/an-ES.xml @@ -51,7 +51,7 @@ Indica los movimientos con a tuya voz Flechas enta los movimientos validos Decir \"Buena partida, bien chugada\" en caso de redota u empaz - Las tuyas preferencias s\'han alzau. + Las tuyas preferencias s\'han alzau. Fe servir la rodeta d\'o rate pa reproducir chugadas Correu electronico diario de notificación con as tuyas partidas per correspondiencia Un presentador ye en vivo diff --git a/translation/dest/preferences/ar-SA.xml b/translation/dest/preferences/ar-SA.xml index 8b19eb1fa61d1..b87ad4315e74e 100644 --- a/translation/dest/preferences/ar-SA.xml +++ b/translation/dest/preferences/ar-SA.xml @@ -51,7 +51,7 @@ الإدخال يتحرك بصوتك سحب الأسهم في اتجاهات صالحة قُل \"مباراة جيدة، لعبت بشكل جيد\" عند الهزيمة أو التعادل - تم حفظ تفضيلاتك. + تم حفظ تفضيلاتك. مرر على الرقعة لإعادة عرض التحركات البريد الإلكتروني اليومي يدرج ألعاب المراسلة الخاصة بك البث مباشر الآن diff --git a/translation/dest/preferences/az-AZ.xml b/translation/dest/preferences/az-AZ.xml index 87bc2f780af0e..b1b929f6a8e81 100644 --- a/translation/dest/preferences/az-AZ.xml +++ b/translation/dest/preferences/az-AZ.xml @@ -41,5 +41,5 @@ Gedişləri klaviatura ilə daxil et Etibarlı gedişlər üçün oxları göstər Məğlub olduqda və ya heç-heçə etdikdə \"Yaxşı oyun idi, yaxşı oynadın\" deyin - Tərcihləriniz saxlanıldı. + Tərcihləriniz saxlanıldı. diff --git a/translation/dest/preferences/be-BY.xml b/translation/dest/preferences/be-BY.xml index ba61c5fbce8a3..ecbd353404f3d 100644 --- a/translation/dest/preferences/be-BY.xml +++ b/translation/dest/preferences/be-BY.xml @@ -51,7 +51,7 @@ Уводзьце хады вашым голасам Маляваць стрэлки толькі да магчымых хадоў Казаць \"Good game, well played\" пасля паразы або нічыі - Вашы налады былі захаваныя. + Вашы налады былі захаваныя. Гартайце колцам мышцы на дошцы, каб прагледзець хады Штодзённае апавяшчэнне па пошце з пералікам вашых гульняў па ліставанні Стрымер вядзе трансляцыю diff --git a/translation/dest/preferences/bg-BG.xml b/translation/dest/preferences/bg-BG.xml index 0084c9945ad30..2d710e8dceb23 100644 --- a/translation/dest/preferences/bg-BG.xml +++ b/translation/dest/preferences/bg-BG.xml @@ -51,7 +51,7 @@ Избирайте ходове с гласа си Ограничи стрелките да показват само допустими ходове Изпратете в чата \"Good game, well played\" след загуба или реми - Вашите предпочитания бяха записани. + Вашите предпочитания бяха записани. За да прегледате ходовете, превъртете колелцето на мишката над дъската Ежедневен имейл със списък на Вашите кореспондентски игри Стриймър започва да излъчва diff --git a/translation/dest/preferences/bn-BD.xml b/translation/dest/preferences/bn-BD.xml index 0b5c913946cc9..684f75e79fdfd 100644 --- a/translation/dest/preferences/bn-BD.xml +++ b/translation/dest/preferences/bn-BD.xml @@ -39,5 +39,5 @@ দুই বর্গে রাজা সরান দাবার নৌকার উপর দিয়ে রাজাকে সরান কী-বোর্ডের সাথে নিবেশ সরান - আপনার পচ্ছন্দগুলি সংরক্ষিত করা হয়েছে. + আপনার পচ্ছন্দগুলি সংরক্ষিত করা হয়েছে. diff --git a/translation/dest/preferences/br-FR.xml b/translation/dest/preferences/br-FR.xml index e00f7c2132b8c..861354235dfca 100644 --- a/translation/dest/preferences/br-FR.xml +++ b/translation/dest/preferences/br-FR.xml @@ -42,7 +42,7 @@ O tiplasañ ar roue deus div garrezenn Diblasañ ar roue war an tour evit rokañ Aozañ an touchennaoueg evit c\'hoari gantañ - Savetaet eo bet ho tibaboù. + Savetaet eo bet ho tibaboù. Un tournamant a vo a-benn nebeut Merdeer Klevet ar c\'hloc\'h diff --git a/translation/dest/preferences/bs-BA.xml b/translation/dest/preferences/bs-BA.xml index dd786e9a02ba5..ff9932647f09d 100644 --- a/translation/dest/preferences/bs-BA.xml +++ b/translation/dest/preferences/bs-BA.xml @@ -50,7 +50,7 @@ Unesite poteze glasom Skačite strelice na važeće poteze Kažite \"Dobra partija, dobro odigrano\" nakon poraza ili remija - Vaše postavke su sačuvane. + Vaše postavke su sačuvane. Pomaknite kotačić miša na ploči da biste ponovili poteze Dnevne obavijesti e-poštom sa spiskom Vaših dopisnih partija Strimer ide uživo diff --git a/translation/dest/preferences/ca-ES.xml b/translation/dest/preferences/ca-ES.xml index b963276ae9556..ae3c012b5f429 100644 --- a/translation/dest/preferences/ca-ES.xml +++ b/translation/dest/preferences/ca-ES.xml @@ -51,7 +51,7 @@ Introduïu moviments amb la vostra veu Apuntar fletxes a jugades legals Dir “Bona partida, ben jugat!” quan perds o fas taules - Les teves preferències s\'han desat. + Les teves preferències s\'han desat. Desplaça\'t amb la rodeta pel tauler per reproduir jugades Correu electrònic diari de notificació amb les vostres partides per correspondència Un streamer que seguiu ha començat una transmissió diff --git a/translation/dest/preferences/ckb-IR.xml b/translation/dest/preferences/ckb-IR.xml index df23d1ea3ceb8..0c40882e50bd5 100644 --- a/translation/dest/preferences/ckb-IR.xml +++ b/translation/dest/preferences/ckb-IR.xml @@ -50,7 +50,7 @@ فرمانی جوڵە بکە بە دەنگت خەتەکان بە ئاراستەیەکی دروستدا ڕابکێشە لە کاتی شکست یان یەکسانبووندا بڵێ \"یارییەکی باش، بە باشی یاریت کرد\" - هەڵبژاردنەکانت هەڵگیراون. + هەڵبژاردنەکانت هەڵگیراون. بۆ دووبارەکردنەوەی جوڵەکان، تختەی شەتڕەنجەکە بەرەو سەر و خوار بجوڵێنە ئیمەیڵی ڕۆژانە کە نامە ناردنەکەت لە یاریەکاندا دەخاتە ڕوو کەسێک دەچێتە پەخشی ڕاستەوخۆ diff --git a/translation/dest/preferences/cs-CZ.xml b/translation/dest/preferences/cs-CZ.xml index 12ed32aee33ad..e110cb43b7893 100644 --- a/translation/dest/preferences/cs-CZ.xml +++ b/translation/dest/preferences/cs-CZ.xml @@ -51,7 +51,7 @@ Zadávání tahů hlasem Přichytit šipky k platným tahům Řekněte \"Good game, well played\" (překlad: \"Dobrá hra, pěkně zahráno\") po porážce nebo remíze - Vaše nastavení bylo uloženo. + Vaše nastavení bylo uloženo. Rolováním na šachovnici přehrát tahy Denní e-mailové oznámení se seznamem vašich korespondenčních her Streamer vysílá živě diff --git a/translation/dest/preferences/cv-CU.xml b/translation/dest/preferences/cv-CU.xml index 24460615902db..51eb619b690e3 100644 --- a/translation/dest/preferences/cv-CU.xml +++ b/translation/dest/preferences/cv-CU.xml @@ -26,7 +26,7 @@ Куҫӑма ҫирӗплетесси Куҫӑн мар вӑйӑсем Вӑрах тата чикӗсӗр - Улӑштарни упраннӑ. + Улӑштарни упраннӑ. Браусӑр Хатӗр diff --git a/translation/dest/preferences/cy-GB.xml b/translation/dest/preferences/cy-GB.xml index 9062ec2106747..26705c85d7015 100644 --- a/translation/dest/preferences/cy-GB.xml +++ b/translation/dest/preferences/cy-GB.xml @@ -39,6 +39,6 @@ Symud y Teyrn dau sgwar Gollwng y Teyrn ar ben y Castell Mewnbynnu symudiadau efo\'r allweddfwrdd - Cadwyd eich dewisiadau. + Cadwyd eich dewisiadau. Porwr diff --git a/translation/dest/preferences/da-DK.xml b/translation/dest/preferences/da-DK.xml index d9aa316a7b817..3e3702f3a11ad 100644 --- a/translation/dest/preferences/da-DK.xml +++ b/translation/dest/preferences/da-DK.xml @@ -51,7 +51,7 @@ Angiv træk med din stemme Markér lovlige træk med pile Sig \"Good game, well played\" (Godt parti, godt spillet) ved nederlag eller remis - Dine præferencer er blevet gemt. + Dine præferencer er blevet gemt. Brug musens scrollhjul på brættet for at afspille træk Daglig e-mailnotifikation med dine korrespondance-spil Streamer går live diff --git a/translation/dest/preferences/de-DE.xml b/translation/dest/preferences/de-DE.xml index 548d823aa9e0f..208a3d69dcc17 100644 --- a/translation/dest/preferences/de-DE.xml +++ b/translation/dest/preferences/de-DE.xml @@ -51,7 +51,7 @@ Züge per Spracheingabe eingeben Pfeile markieren immer mögliche Züge Sage \"Good game, well played\" (Gute Partie, gut gespielt) nach einer Niederlage oder einem Remis - Deine Einstellungen wurden gespeichert. + Deine Einstellungen wurden gespeichert. Auf dem Brett scrollen, um Züge durchzugehen Tägliche E-Mail-Benachrichtigung, die deine Fernschachpartien auflistet Streamer ist live diff --git a/translation/dest/preferences/el-GR.xml b/translation/dest/preferences/el-GR.xml index 6e0c4d2c60e12..bfd6c8a077c14 100644 --- a/translation/dest/preferences/el-GR.xml +++ b/translation/dest/preferences/el-GR.xml @@ -50,7 +50,7 @@ Εισαγωγή κινήσεων με τη φωνή σας Αγκύρωση βελών σε έγκυρες κινήσεις Αποστολή «Good game, well played» (Ωραίο παιχνίδι, καλά παιγμένο) μετά από ήττα ή ισοπαλία - Οι προτιμήσεις σας αποθηκεύτηκαν. + Οι προτιμήσεις σας αποθηκεύτηκαν. Κυλίστε τον δρομέα πάνω στη σκακιέρα για να ξαναδείτε κινήσεις Να δέχομαι ημερήσια ειδοποίηση ηλεκτρονικού ταχυδρομείου για τα παιχνίδια διά αλληλογραφίας μου Streamer εκπέμπει ζωντανά diff --git a/translation/dest/preferences/en-US.xml b/translation/dest/preferences/en-US.xml index dd179f111c5bd..e9c0b05c84aa5 100644 --- a/translation/dest/preferences/en-US.xml +++ b/translation/dest/preferences/en-US.xml @@ -51,7 +51,7 @@ Input moves with your voice Snap arrows to valid moves Say \"Good game, well played\" upon defeat or draw - Your preferences have been saved. + Your preferences have been saved. Scroll on the board to replay moves Daily mail notification listing your correspondence games Streamer goes live diff --git a/translation/dest/preferences/eo-UY.xml b/translation/dest/preferences/eo-UY.xml index 91a5abf9c3d0d..5f0a80a4bbcac 100644 --- a/translation/dest/preferences/eo-UY.xml +++ b/translation/dest/preferences/eo-UY.xml @@ -51,7 +51,7 @@ Enigi movojn per via voĉo Montri per sagoj nur validajn movojn Skribi \"Good game, well played\" post malvenko aŭ egalvenko - Viaj preferoj estis konservitaj. + Viaj preferoj estis konservitaj. Uzi rulumilon sur la tabelo por ripeti movojn Tage retpoŝta sciigo listante viajn korespondajn partiojn Filmprezentisto iĝas vive diff --git a/translation/dest/preferences/es-ES.xml b/translation/dest/preferences/es-ES.xml index e2f5b3623478a..d82970c8dc5cd 100644 --- a/translation/dest/preferences/es-ES.xml +++ b/translation/dest/preferences/es-ES.xml @@ -51,7 +51,7 @@ Realiza movimientos con tu voz Adherir flechas a movimientos válidos Decir \"Good game, well played\" (Buena partida, bien jugada) al perder o empatar - Tus preferencias se han guardado. + Tus preferencias se han guardado. Usa la rueda de desplazamiento sobre el tablero para volver a mostrar los movimientos Notificación diaria por correo listando tus partidas por correspondencia El presentador está en vivo diff --git a/translation/dest/preferences/et-EE.xml b/translation/dest/preferences/et-EE.xml index 5cfe227c309cf..be2b2b7eb8221 100644 --- a/translation/dest/preferences/et-EE.xml +++ b/translation/dest/preferences/et-EE.xml @@ -47,7 +47,7 @@ Sisesta käigud klaviatuuril Liiguta nooled kehtivate käikude juurde Ütle kaotuse või viigi korral \"Good game, well played\" (Hea mäng, hästi mängitud) - Teie eelistused on salvestatud. + Teie eelistused on salvestatud. Käikude kordamiseks keri laual Igapäevane e-kiri, mis sisaldab pooleliolevaid kirjavahetusmänge Striimija alustab otseülekannet diff --git a/translation/dest/preferences/eu-ES.xml b/translation/dest/preferences/eu-ES.xml index 805cacbb4642b..c4f5875657e42 100644 --- a/translation/dest/preferences/eu-ES.xml +++ b/translation/dest/preferences/eu-ES.xml @@ -51,7 +51,7 @@ Egin jokaldiak zure ahotsarekin Marraztutako geziak legezko jokaldietara mugatu Txateam \"Good game, well played\" esan partida galdu edo berdintzean - Zure ezarpenak ondo gorde dira. + Zure ezarpenak ondo gorde dira. Mugitu taula gainean jokaldiak ikusteko Jaso posta elektronikoz zure posta-bidezko partiden jakinarazpenen zerrenda egunero Streamerra zuzenean dago diff --git a/translation/dest/preferences/fa-IR.xml b/translation/dest/preferences/fa-IR.xml index c93fe8f6d08cd..6b4cc5992640e 100644 --- a/translation/dest/preferences/fa-IR.xml +++ b/translation/dest/preferences/fa-IR.xml @@ -51,7 +51,7 @@ حرکات را با صدای خود وارد کنید چسبیدن پیکان‌ها به حرکت‌های ممکن گفتن \"بازی خوبی بود، خوب بازی کردی\" در هنگام باخت یا تساوی - تغییرات شما ذخیره شده است + تغییرات شما ذخیره شده است اسکرول کردن روی صفحه برای مشاهده مجدد حرکت‌ها ایمیل های روزانه که بازی های شبیه شما را به صورت لیست درمی‌آورند استریمر شروع به فعالیت کرد diff --git a/translation/dest/preferences/fi-FI.xml b/translation/dest/preferences/fi-FI.xml index 4dab92f06f389..10b162beb1126 100644 --- a/translation/dest/preferences/fi-FI.xml +++ b/translation/dest/preferences/fi-FI.xml @@ -51,7 +51,7 @@ Syötä siirtosi puheella Merkitse mahdolliset siirrot nuolilla Sano \"Good game, well played\" (suom. \"Hyvä peli, hyvin pelattu\") tasapelin tai tappion jälkeen - Asetuksesi on tallennettu. + Asetuksesi on tallennettu. Vierittämällä laudan yllä voit katsoa siirtoja uudelleen Päivittäinen sähköposti-ilmoitus, jossa listataan kirjeshakkipelisi Striimaaja aloittaa striimin diff --git a/translation/dest/preferences/fo-FO.xml b/translation/dest/preferences/fo-FO.xml index a93e2a7cd6adf..3280ea6c3dcdf 100644 --- a/translation/dest/preferences/fo-FO.xml +++ b/translation/dest/preferences/fo-FO.xml @@ -41,5 +41,5 @@ Skriva leikirnar við lyklaborðinum Vís lógligar leikir við pílum Sig \"Gott talv, væl telvað,\" tá ið tú vinnur ella telvar javnt - Tínar stillingar eru goymdar. + Tínar stillingar eru goymdar. diff --git a/translation/dest/preferences/fr-FR.xml b/translation/dest/preferences/fr-FR.xml index 73ebb3822f8b5..f38445a6d3cc5 100644 --- a/translation/dest/preferences/fr-FR.xml +++ b/translation/dest/preferences/fr-FR.xml @@ -50,8 +50,8 @@ Saisir les coups au clavier Utiliser la reconnaissance vocale pour déplacer les pièces Restreindre les flèches aux coups valides - Dire \"Good game, well played\" c.-à.-d. \"Bonne partie, bien joué\", en cas de défaite ou de nulle - Vos préférences ont été sauvegardées. + Dire \"Bonne partie, bien jouée\" en cas de défaite ou de nulle + Vos préférences ont été sauvegardées. Parcourez les coups avec la molette de la souris sur l\'échiquier Email quotidien de la liste de vos parties par correspondance Le diffuseur passe en direct diff --git a/translation/dest/preferences/ga-IE.xml b/translation/dest/preferences/ga-IE.xml index bda4761544a13..34ddebba76d90 100644 --- a/translation/dest/preferences/ga-IE.xml +++ b/translation/dest/preferences/ga-IE.xml @@ -47,7 +47,7 @@ Bogann ionchur leis an méarchlár Snap saigheada chuig bearta dlithiúil Abair \"Cluiche maith, Grma\" ar aon cluiche cailte nó cothroma - Sábháladh do chuid sainroghanna. + Sábháladh do chuid sainroghanna. Scrollaigh ar an gclár chun bearta a athimirt Ríomhphost laethúil ag liostú do chluichí comhfhreagrais Sraoilleán ag craoladh beo diff --git a/translation/dest/preferences/gd-GB.xml b/translation/dest/preferences/gd-GB.xml index 322060234891e..cd34280054959 100644 --- a/translation/dest/preferences/gd-GB.xml +++ b/translation/dest/preferences/gd-GB.xml @@ -16,5 +16,5 @@ Nuair a bhios < 30 diog a thìde air fhàgail Dearbhadh a\' ghluasaid Geamannan co-sgrìobhaidh - Chaidh na roghainnean agad a shàbhaladh. + Chaidh na roghainnean agad a shàbhaladh. diff --git a/translation/dest/preferences/gl-ES.xml b/translation/dest/preferences/gl-ES.xml index 9c85f14c7bc8f..0ce24861ccdff 100644 --- a/translation/dest/preferences/gl-ES.xml +++ b/translation/dest/preferences/gl-ES.xml @@ -51,7 +51,7 @@ Introdución de xogadas coa voz Adherir frechas a movementos válidos Dicir \"Good game, well played\" (Boa partida, ben xogada) ao perder ou empatar - As túas preferencias foron gardadas. + As túas preferencias foron gardadas. Usar a roda do rato para amosar os movementos Notificación diaria por email coas túas partidas por correspondencia Un presentador comeza unha transmisión en directo diff --git a/translation/dest/preferences/gsw-CH.xml b/translation/dest/preferences/gsw-CH.xml index d41f64212685e..b22876d55c232 100644 --- a/translation/dest/preferences/gsw-CH.xml +++ b/translation/dest/preferences/gsw-CH.xml @@ -51,7 +51,7 @@ Züg per Sprachigab Möglichi Züg werded mit Pfil azeigt Säg \"guet gschpillt\" nach ere Niderlag oder bi me Remis - Dini Ischtellige sind gschpeicheret. + Dini Ischtellige sind gschpeicheret. Mit em Muszeiger uf em Brätt, chasch mit em Musrad all Züg vor- und zrugg scrolle Täglichi E-Mail-Benachrichtigung, wo Dini Fernschachpartie uflischtet De Streamer gaht live diff --git a/translation/dest/preferences/he-IL.xml b/translation/dest/preferences/he-IL.xml index 26dcc85739804..9fa30f03c2ea3 100644 --- a/translation/dest/preferences/he-IL.xml +++ b/translation/dest/preferences/he-IL.xml @@ -38,7 +38,7 @@ לחצו על מקש <ctrl> בזמן הקידום כדי להשבית זמנית את ההכתרה האוטומטית כאשר מבצעים קדם-מהלך הכרז על תיקו בחזרה משולשת באופן אוטומטי - כשהזמן הנותר קטן מ-30 שניות + כשהזמן הנותר קטן מ־30 שניות אישור המהלכים ניתן לביטול במהלך המשחק באמצעות תפריט הלוח במשחקים בהתכתבות @@ -51,7 +51,7 @@ ביצוע מהלכים באמצעות דיבור התאמת החצים למסעים חוקיים כתבו ״Good game, well played\" בצ׳אט לאחר הפסד או תיקו (בתרגום חופשי: משחק יפה, שיחקת טוב) - העדפותיך נשמרו. + העדפותיך נשמרו. גלול על גבי הלוח כדי להראות מהלכים קודמים הודעת מייל יומית עם רשימת המשחקים שלך בהתכתבות משדר עולה לשידור חי @@ -62,8 +62,8 @@ הזמנות למשחקים טורניר מתחיל בקרוב אוזל הזמן במשחק התכתבות - התראות פעמון בליצ׳ס - התראה למכשיר גם כשאינך מחובר/ת לליצ׳ס + התראות פעמון ב־Lichess + התראה למכשיר גם כשאינך מחובר/ת ל־Lichess דפדפן מכשיר השמע צליל עבור התראות פעמון diff --git a/translation/dest/preferences/hi-IN.xml b/translation/dest/preferences/hi-IN.xml index 1a396b76c5174..595cc550da9fe 100644 --- a/translation/dest/preferences/hi-IN.xml +++ b/translation/dest/preferences/hi-IN.xml @@ -49,7 +49,7 @@ कंप्यूटर कीबोर्ड के साथ इनपुट करें मान्य चाल के लिए स्नैप तीर हार या ड्रॉ पर \"अच्छा खेल, अच्छा खेला\" कहें - आपकी प्राथमिकताएं सेव कर ली गई हैं + आपकी प्राथमिकताएं सेव कर ली गई हैं पिछली चालें पुनः देखने के लिए बोर्ड पर स्क्राल करें आपके पत्राचार खेलों को सूचीबद्ध करने वाली दैनिक मेल अधिसूचना diff --git a/translation/dest/preferences/hr-HR.xml b/translation/dest/preferences/hr-HR.xml index 4af87974f3d95..17e45ae488cfd 100644 --- a/translation/dest/preferences/hr-HR.xml +++ b/translation/dest/preferences/hr-HR.xml @@ -48,7 +48,7 @@ Omogući unošenje poteza tipkovnicom Crtaj strelice za planiranje budućih poteza Reci \"Dobra partija, odlićno odigrano\" kad izgubiš ili odigraš remi - Tvoje promjene su spremljene. + Tvoje promjene su spremljene. Pomakni kotačić miša iznad ploče za pregled poteza Dnevna obavijest putem pošte s popisom vaših dopisnih igara Streamer ide uživo diff --git a/translation/dest/preferences/hu-HU.xml b/translation/dest/preferences/hu-HU.xml index a5fb439067aa9..edcaea29cc509 100644 --- a/translation/dest/preferences/hu-HU.xml +++ b/translation/dest/preferences/hu-HU.xml @@ -50,7 +50,7 @@ Lépj a hangod segítségével Nyilak illesztése a szabályos lépésekhez \"Good game, well played\" üzenet küldése döntetlen vagy vereség esetén - Beállítások elmentve. + Beállítások elmentve. Lépések visszajátszása görgetéssel Napi email a folyamatban lévő levelezős játszmákról Egy streamer műsort ad diff --git a/translation/dest/preferences/hy-AM.xml b/translation/dest/preferences/hy-AM.xml index 974fe22eecd53..3dea25e3bc800 100644 --- a/translation/dest/preferences/hy-AM.xml +++ b/translation/dest/preferences/hy-AM.xml @@ -48,7 +48,7 @@ Քայլերի ներմուծումը ձայնի միջոցով Սլաքներով ցույց տալ միայն թույլատրելի քայլերը Պարտությունից կամ ոչ-ոքիից հետո զրուցարանում գրել. «Good game, well played» - Ձեր նախընտրությունները պահպանված են + Ձեր նախընտրությունները պահպանված են Քայլերը դիտելու համար մկնիկի անիվը պտտեք խաղատախտակի վրա Ստուդիայի հրավեր Նամակագրական խաղին առնչվող թարմացումներ diff --git a/translation/dest/preferences/ia-IA.xml b/translation/dest/preferences/ia-IA.xml index 9410d9dee0774..4a73b8b10bf24 100644 --- a/translation/dest/preferences/ia-IA.xml +++ b/translation/dest/preferences/ia-IA.xml @@ -41,5 +41,5 @@ Entrata de movimentos con le claviero Restringer le sagittas al movimentos valide Dicer \"Good game, well played\" (Bon partita, ben jocate) post defaite o partita nulle - Tu preferentias ha essite salveguardate. + Tu preferentias ha essite salveguardate. diff --git a/translation/dest/preferences/id-ID.xml b/translation/dest/preferences/id-ID.xml index 183b01b8bd692..960c4ce754b0a 100644 --- a/translation/dest/preferences/id-ID.xml +++ b/translation/dest/preferences/id-ID.xml @@ -48,7 +48,7 @@ Melangkah dengan menggunakan keyboard Arahkan panah ke arah yang benar Ucapkan \"Good game, well played\" apabila kalah atau seri - Pengaturan telah disimpan. + Pengaturan telah disimpan. Gulir pada papan untuk mengulang gerakan Email harian yang berisi daftar permainan korespondensi anda Streamer memulai siaran diff --git a/translation/dest/preferences/is-IS.xml b/translation/dest/preferences/is-IS.xml index 06200c32b3d63..8a277f82b2bd1 100644 --- a/translation/dest/preferences/is-IS.xml +++ b/translation/dest/preferences/is-IS.xml @@ -40,5 +40,5 @@ Færa kóng á hrókinn Leika með lyklaborðinu Segja \"Good game, well played\" sjálfkrafa eftir tapi eða jafntefli - Stillingar þínar voru vistaðar + Stillingar þínar voru vistaðar diff --git a/translation/dest/preferences/it-IT.xml b/translation/dest/preferences/it-IT.xml index 6b2170903c626..6b15a77374e0b 100644 --- a/translation/dest/preferences/it-IT.xml +++ b/translation/dest/preferences/it-IT.xml @@ -51,7 +51,7 @@ Muovi con la tua voce Collega le frecce a mosse valide Di\' \"Good game, well played\" alla sconfitta o al pareggio - Le tue preferenze sono state salvate. + Le tue preferenze sono state salvate. Scorri sulla scacchiera per riprodurre le mosse Notifica di posta giornaliera che elenca le tue partite per corrispondenza Lo streamer va in diretta diff --git a/translation/dest/preferences/ja-JP.xml b/translation/dest/preferences/ja-JP.xml index 8245c48a65467..4e8ab20b829b6 100644 --- a/translation/dest/preferences/ja-JP.xml +++ b/translation/dest/preferences/ja-JP.xml @@ -51,7 +51,7 @@ 手を声で入力 可能な手を矢印で表示 負けかドローの際に「Good game, well played」と言う - 設定が保存されました。 + 設定が保存されました。 ボード上スクロールで手を再現 通信戦の対局をリストにした毎日のメール通知 配信を始めた時 diff --git a/translation/dest/preferences/jbo-EN.xml b/translation/dest/preferences/jbo-EN.xml index a4e34ee89c6fa..b5ed492650dab 100644 --- a/translation/dest/preferences/jbo-EN.xml +++ b/translation/dest/preferences/jbo-EN.xml @@ -26,5 +26,5 @@ masno nunkei lo tadji be lo badydi\'ulujnunmu\'u jarco tu\'a lo nunmu\'u se pi\'o lo batkyfoi - lo selnei be do pu se rejgau + lo selnei be do pu se rejgau diff --git a/translation/dest/preferences/ka-GE.xml b/translation/dest/preferences/ka-GE.xml index 9ea212c14df23..fb70f038efb18 100644 --- a/translation/dest/preferences/ka-GE.xml +++ b/translation/dest/preferences/ka-GE.xml @@ -33,5 +33,5 @@ როქის მეთოდი მეფის ორი უჯრით გადაადგილება მეფის ეტლზე მიტანა - თქვენი პარამეტრები დამახსოვრებულია. + თქვენი პარამეტრები დამახსოვრებულია. diff --git a/translation/dest/preferences/kk-KZ.xml b/translation/dest/preferences/kk-KZ.xml index b3ee50191b005..cf6e06eca8585 100644 --- a/translation/dest/preferences/kk-KZ.xml +++ b/translation/dest/preferences/kk-KZ.xml @@ -50,7 +50,7 @@ Қадамды дауыспен жасау Нұсқағыш тек заңды жүрістерге ғана жабысады Жеңіліс не тепе-теңдіктен кейін \"Жақсы ойын, қызық болды\" деп айту - Баптауыңыз сақталды. + Баптауыңыз сақталды. Тақта бетінде тіңтуір айналдырумен жүрістерді қайта көрсету Хат-хабарлы ойындарыңыз туралы күнде поштаңызға ескертпе жіберу Стрим басталды diff --git a/translation/dest/preferences/kmr-TR.xml b/translation/dest/preferences/kmr-TR.xml index f28fbcc9a3c50..7cda724860db8 100644 --- a/translation/dest/preferences/kmr-TR.xml +++ b/translation/dest/preferences/kmr-TR.xml @@ -39,5 +39,5 @@ Şahê du çarçik bide hereketkirin Şahê bi ser kelehê de kaş bike Hemlekirina bi textenivîsê aktîv bike - Tercîhên te hate qeydkirin. + Tercîhên te hate qeydkirin. diff --git a/translation/dest/preferences/kn-IN.xml b/translation/dest/preferences/kn-IN.xml index 3c4fde2808905..533a7e3cbe205 100644 --- a/translation/dest/preferences/kn-IN.xml +++ b/translation/dest/preferences/kn-IN.xml @@ -50,7 +50,7 @@ ನಿಮ್ಮ ಧ್ವನಿಯೊಂದಿಗೆ ಇನ್‌ಪುಟ್ ಚಲಿಸುತ್ತದೆ ಮಾನ್ಯ ಚಲನೆಗಳಿಗೆ ಬಾಣಗಳನ್ನು ಸ್ನ್ಯಾಪ್ ಮಾಡಿ ಸೋಲು ಅಥವಾ ಡ್ರಾ ಆದ ಮೇಲೆ \"ಒಳ್ಳೆಯ ಆಟ, ಚೆನ್ನಾಗಿ ಆಡಿದೆ\" ಎಂದು ಹೇಳಿ - ನಿಮ್ಮ ಪ್ರಾಶಸ್ತ್ಯಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ. + ನಿಮ್ಮ ಪ್ರಾಶಸ್ತ್ಯಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ. ಚಲನೆಗಳನ್ನು ಮರುಪಂದ್ಯ ಮಾಡಲು ಬೋರ್ಡ್ ಮೇಲೆ ಸ್ಕ್ರಾಲ್ ಮಾಡಿ ನಿಮ್ಮ ಪತ್ರವ್ಯವಹಾರದ ಆಟಗಳನ್ನು ಪಟ್ಟಿ ಮಾಡುವ ದೈನಂದಿನ ಇಮೇಲ್ ಸ್ಟ್ರೀಮರ್ ಲೈವ್ ಆಗುತ್ತದೆ diff --git a/translation/dest/preferences/ko-KR.xml b/translation/dest/preferences/ko-KR.xml index 4f65a1d37574e..f8702febe718d 100644 --- a/translation/dest/preferences/ko-KR.xml +++ b/translation/dest/preferences/ko-KR.xml @@ -50,7 +50,7 @@ 음성으로 기물 이동 적법한 움직임에만 화살표를 그림 패배하거나 무승부 시 \"Good game, well played\"라고 말합니다. - 설정이 저장되었습니다. + 설정이 저장되었습니다. 보드에서 스크롤을 해서 수를 앞 뒤로 이동 매일 일간 게임의 목록을 보여주는 알림 메일을 받기 스트리머가 생방송 시작 diff --git a/translation/dest/preferences/la-LA.xml b/translation/dest/preferences/la-LA.xml index fa166682833d1..584a0d5f3eebf 100644 --- a/translation/dest/preferences/la-LA.xml +++ b/translation/dest/preferences/la-LA.xml @@ -40,5 +40,5 @@ Regem movere in secundum quadrum Regem super turrim movere \"Bene lusimus illum lusum\" dicere te victum vel ludu ancipe - Praeposititiones tuae conservatae sunt. + Praeposititiones tuae conservatae sunt. diff --git a/translation/dest/preferences/lb-LU.xml b/translation/dest/preferences/lb-LU.xml index a757841d27a21..664ccb09baf87 100644 --- a/translation/dest/preferences/lb-LU.xml +++ b/translation/dest/preferences/lb-LU.xml @@ -50,7 +50,7 @@ Zich per Sproocherkennung aginn Feiler können just legal Zich weisen No Defaite oder Remis \"Good game, well played\" (Gudd Partie, gudd gespillt) soen - Deng Astellungen goufen gespäichert. + Deng Astellungen goufen gespäichert. Scroll iwwer d\'Briet fir Zich nozespillen Deegleg Email mat Lëscht vun Korrespondenzpartien Streamer geet live diff --git a/translation/dest/preferences/lt-LT.xml b/translation/dest/preferences/lt-LT.xml index f01344945512c..bb0fc26af08ae 100644 --- a/translation/dest/preferences/lt-LT.xml +++ b/translation/dest/preferences/lt-LT.xml @@ -50,7 +50,7 @@ Įvesti ėjimus balsu Rodykles užfiksuoti ties leistinais ėjimais Nepamirškite nugalėti ar po lygiųjų pasakyti, \"Gera partija, ačiū\" - Jūsų nuostatos buvo išsaugotos. + Jūsų nuostatos buvo išsaugotos. Sukite ratuką ant lentos norėdami dar kartą pamatyti ėjimus Kasdieniame laiške pateikti korespondensinių žaidimų išrašus Transliuotojas pradeda transliaciją diff --git a/translation/dest/preferences/lv-LV.xml b/translation/dest/preferences/lv-LV.xml index d660f87ef3f9d..100f8eb087c06 100644 --- a/translation/dest/preferences/lv-LV.xml +++ b/translation/dest/preferences/lv-LV.xml @@ -48,7 +48,7 @@ Ievadīt gājienus ar tastatūru Pievilkt bultas pie iespējamiem gājieniem Sūtīt \"Good game, well played\" (angļ. val. \"Laba partija, labi nospēlēta\") pēc uzvaras vai neizšķirta - Jūsu uzstādījumi ir saglabāti. + Jūsu uzstādījumi ir saglabāti. Ritiniet peli virs galdiņa, lai skatītu spēles gaitu E-pasta ziņojums reizi dienā par jūsu korespondencšaha spēlēm Straumētājs sāk tiešraidi diff --git a/translation/dest/preferences/mk-MK.xml b/translation/dest/preferences/mk-MK.xml index 81307c0455d5d..ba6967208357d 100644 --- a/translation/dest/preferences/mk-MK.xml +++ b/translation/dest/preferences/mk-MK.xml @@ -41,5 +41,5 @@ Помести го кралот две полиња Помести го кралот врз топот Внеси потези со тастатура - Вашите поставки се зачувани. + Вашите поставки се зачувани. diff --git a/translation/dest/preferences/ml-IN.xml b/translation/dest/preferences/ml-IN.xml index 681172a51087b..f2f584348857c 100644 --- a/translation/dest/preferences/ml-IN.xml +++ b/translation/dest/preferences/ml-IN.xml @@ -37,5 +37,5 @@ രാജാവിനെ രണ്ടു കാലങ്ങൾ നീക്കുക തേരിനു മുകളിൽ രാജാവിനെ വയ്ക്കുക കീബോർഡ് മുഖേന നീക്കങ്ങൾ നൽകുക - നിങ്ങളുടെ ക്രമീകരണങ്ങൾ സേവ് ചെയ്തു. + നിങ്ങളുടെ ക്രമീകരണങ്ങൾ സേവ് ചെയ്തു. diff --git a/translation/dest/preferences/mn-MN.xml b/translation/dest/preferences/mn-MN.xml index d8dac8949b237..6a4e1b2f27fdf 100644 --- a/translation/dest/preferences/mn-MN.xml +++ b/translation/dest/preferences/mn-MN.xml @@ -40,7 +40,7 @@ Ноёноо хоёр нүүх Ноёноо тэрэгрүүгээ нүүх Гарнаас нүүдэл оруулах - Таны шинэчлэгдсэн тохиргоо хадгалагдлаа. + Таны шинэчлэгдсэн тохиргоо хадгалагдлаа. Саналууд Тэмцээн удахгүй эхэллээ яараарай!! diff --git a/translation/dest/preferences/mr-IN.xml b/translation/dest/preferences/mr-IN.xml index 9a1fd1ef41f13..c04efc0e0619b 100644 --- a/translation/dest/preferences/mr-IN.xml +++ b/translation/dest/preferences/mr-IN.xml @@ -51,7 +51,7 @@ तुमच्या आवाजाने इनपुट द्या बाण फक्त वैध चालींसाठीच मर्यादित पराभवानंतर किंवा बरोबरीनंतर \"चांगला खेळ झाला, उत्तम खेळला\" असे म्हणा - तुमची प्राधान्ये जतन केली गेली आहेत. + तुमची प्राधान्ये जतन केली गेली आहेत. चाली परत बघण्यासाठी पटावर स्क्रोल करा तुमच्या दीर्घकालीन खेळांची यादी असलेली दैनिक ई-मेल सुचना जेव्हा स्ट्रीमर प्रसरण सुरू करतो diff --git a/translation/dest/preferences/nb-NO.xml b/translation/dest/preferences/nb-NO.xml index 41be5e71246d9..05b6b8df90fb5 100644 --- a/translation/dest/preferences/nb-NO.xml +++ b/translation/dest/preferences/nb-NO.xml @@ -51,7 +51,7 @@ Gi trekk med stemmen Piler viser gyldige trekk Si «Good game, well played» («Godt parti, bra spilt») etter tap eller remis - Innstillingene dine er lagret. + Innstillingene dine er lagret. Bruk musehjulet for å spille av trekk Daglig oversikt over fjernsjakkpartiene dine tilsendt på e-post Strømmer begynner å strømme diff --git a/translation/dest/preferences/ne-NP.xml b/translation/dest/preferences/ne-NP.xml index 4c5e2fbd32b52..93f75907789b2 100644 --- a/translation/dest/preferences/ne-NP.xml +++ b/translation/dest/preferences/ne-NP.xml @@ -37,5 +37,5 @@ राजा दुई कदम चाल्ने राजा हात्तीमा चाल्ने किबोर्डबाट चाल चलौं - तपाइका प्राथमिकता सेभ गरिएको छ। + तपाइका प्राथमिकता सेभ गरिएको छ। diff --git a/translation/dest/preferences/nl-NL.xml b/translation/dest/preferences/nl-NL.xml index 656afb815ea72..f75f736952286 100644 --- a/translation/dest/preferences/nl-NL.xml +++ b/translation/dest/preferences/nl-NL.xml @@ -51,7 +51,7 @@ Voer zetten in met je stem Geldige zetten markeren met pijlen Zeg \"Goede partij, goed gespeeld\" bij verlies of remise - Uw voorkeuren werden opgeslagen. + Uw voorkeuren werden opgeslagen. Scroll op het bord om zetten opnieuw af te spelen Dagelijkse e-mailmeldingen met de lijst van jouw correspondentiepartijen Streamer gaat live diff --git a/translation/dest/preferences/nn-NO.xml b/translation/dest/preferences/nn-NO.xml index 74fded53c6cc9..a7cc65359eee0 100644 --- a/translation/dest/preferences/nn-NO.xml +++ b/translation/dest/preferences/nn-NO.xml @@ -51,7 +51,7 @@ Oppgje trekk med røysta Marker lovlege trekk med piler Sei \"Bra parti, godt spela\" etter nederlag eller remis - Preferansane dine er lagra. + Preferansane dine er lagra. Rull (\"scroll med musehjulet\") ned på brettet for å spela trekk omatt Dagleg e-postmelding med ei liste av fjernsjakkpartia dine Strøyminga er i gang diff --git a/translation/dest/preferences/or-IN.xml b/translation/dest/preferences/or-IN.xml index 394bf17625e61..4eaa4870b2678 100644 --- a/translation/dest/preferences/or-IN.xml +++ b/translation/dest/preferences/or-IN.xml @@ -10,6 +10,6 @@ ଅଧିକ ସମୟ ଦିଅନ୍ତୁ ଖେଳ ଆଚରଣ ଆପଣଙ୍କ ସ୍ୱର ସହିତ ଇନପୁଟ୍ ଚଳନ - ଆପଣଙ୍କ ଅଗ୍ରାଧିକାର ସଞ୍ଚୟ ହୋଇଛି। + ଆପଣଙ୍କ ଅଗ୍ରାଧିକାର ସଞ୍ଚୟ ହୋଇଛି। ଆପଣଙ୍କ ଖେଳଗୁଡ଼ିକୁ ତାଲିକାଭୁକ୍ତ କରୁଥିବା ଦୈନିକ ମେଲ୍ ବିଜ୍ଞପ୍ତି diff --git a/translation/dest/preferences/pl-PL.xml b/translation/dest/preferences/pl-PL.xml index 9c7adac0e1388..ded862bd60012 100644 --- a/translation/dest/preferences/pl-PL.xml +++ b/translation/dest/preferences/pl-PL.xml @@ -51,7 +51,7 @@ Wykonywanie posunięć za pomocą głosu Wyrównuj strzałki do legalnych ruchów Napisz \"Good game, well played\" po przegranej lub remisie - Twoje ustawienia zostały zapisane. + Twoje ustawienia zostały zapisane. Przewiń myszką, aby powtórzyć ruchy Codzienne powiadomienia mailowe z listą Twoich partii korespondencyjnych Streamer jest na żywo diff --git a/translation/dest/preferences/pt-BR.xml b/translation/dest/preferences/pt-BR.xml index 4c14848d3fd03..ae78fbbec1751 100644 --- a/translation/dest/preferences/pt-BR.xml +++ b/translation/dest/preferences/pt-BR.xml @@ -51,7 +51,7 @@ Mova as peças com sua voz Insira setas para movimentos válidos Diga \"Bom jogo, bem jogado\" após a derrota ou empate - Suas preferências foram salvas. + Suas preferências foram salvas. Use o scroll do mouse no tabuleiro para ir passando as jogadas Email diário listando seus jogos por correspondência Streamer começou uma transmissão ao vivo diff --git a/translation/dest/preferences/pt-PT.xml b/translation/dest/preferences/pt-PT.xml index db7eb38e82285..2ef5ddbd5f3f4 100644 --- a/translation/dest/preferences/pt-PT.xml +++ b/translation/dest/preferences/pt-PT.xml @@ -51,7 +51,7 @@ Insira movimentos com a sua voz Alinhar as setas para sítios para onde as peças se podem mover Dizer \"Good game, well played\" (Bom jogo, bem jogado) após uma derrota ou empate - As tuas preferências foram guardadas. + As tuas preferências foram guardadas. Rolar no tabuleiro para repetir os movimentos Notificações diárias por email listando seus jogos por correspondência Streamer começou uma transmissão ao vivo diff --git a/translation/dest/preferences/ro-RO.xml b/translation/dest/preferences/ro-RO.xml index 92955249e5c59..7898c0013df79 100644 --- a/translation/dest/preferences/ro-RO.xml +++ b/translation/dest/preferences/ro-RO.xml @@ -51,7 +51,7 @@ Execută mișcările cu vocea Trage săgețile la mutările valide Spune \"Joc bun, bine jucat\" la înfrângere sau la remiză - Preferințele tale au fost salvate + Preferințele tale au fost salvate Derulează pe tablă pentru a rejuca mutările Notificare zilnică prin email cu lista jocurilor prin corespondență Un streamer e live diff --git a/translation/dest/preferences/ru-RU.xml b/translation/dest/preferences/ru-RU.xml index ebc856b2585fd..5b86dccc79b85 100644 --- a/translation/dest/preferences/ru-RU.xml +++ b/translation/dest/preferences/ru-RU.xml @@ -51,7 +51,7 @@ Вводить ходы голосом Показывать стрелками только допустимые ходы Писать в чат “Good game, well played” после поражения или ничьей - Ваши настройки сохранены. + Ваши настройки сохранены. Прокручивайте колесо мыши над доской, чтобы смотреть ходы Ежедневно присылать на почту список ваших игр по переписке Стример начинает трансляцию diff --git a/translation/dest/preferences/sk-SK.xml b/translation/dest/preferences/sk-SK.xml index fcd635fe77711..88d27e7122cb2 100644 --- a/translation/dest/preferences/sk-SK.xml +++ b/translation/dest/preferences/sk-SK.xml @@ -51,7 +51,7 @@ Zadávanie ťahov pomocou hlasu Zobraziť šípky pri možných ťahoch Napíš \"Good game, well played\" (v preklade: Dobrá partia, pekne zahrané) po prehre alebo remíze - Vaše nastavenia boli uložené. + Vaše nastavenia boli uložené. Rolovaním po šachovnici prehrávať ťahy Denný mailový výpis Vašich korešpondenčných partií Streamer začal vysielať diff --git a/translation/dest/preferences/sl-SI.xml b/translation/dest/preferences/sl-SI.xml index 3283d38e3441b..f7704bbbb51de 100644 --- a/translation/dest/preferences/sl-SI.xml +++ b/translation/dest/preferences/sl-SI.xml @@ -48,7 +48,7 @@ Vnos potez z vašim glasom Postavi puščice po veljavnih potezah Reci \"Dobra igra, dobro odigrano\" ob porazu ali remiju - Nastavitve so bile shranjene. + Nastavitve so bile shranjene. Pomaknite se po plošči za predvajanje potez Dnevno obvestilo po pošti z naštevanjem vaših korespondenčnih iger Streamer je začel oddajati v živo diff --git a/translation/dest/preferences/sq-AL.xml b/translation/dest/preferences/sq-AL.xml index 1401b230ab43b..ee96c27abd571 100644 --- a/translation/dest/preferences/sq-AL.xml +++ b/translation/dest/preferences/sq-AL.xml @@ -48,7 +48,7 @@ Jepni lëvizje përmes tastiere Kryeni lëvizje përmes zërit tuaj Shkruaj “Lojë e mirë, bukur luajtët” pas barazimit ose humbjes - Parapëlqimet tuaja u ruajtën. + Parapëlqimet tuaja u ruajtën. Rrëshqitni nëpër tabelë që të riluhen lëvizje Njoftim i përditshëm me email, që paraqet lojërat tuaja me korrespondencë Mesazh i ri te Të marrë diff --git a/translation/dest/preferences/sr-SP.xml b/translation/dest/preferences/sr-SP.xml index efe53d28d4a1e..567fae577d043 100644 --- a/translation/dest/preferences/sr-SP.xml +++ b/translation/dest/preferences/sr-SP.xml @@ -40,5 +40,5 @@ Помери краља на топа Уноеси потезе са тастатуром Кажи \"Добра партија, добро одиграно\" након пораза или нерешеног - Ваше преференце су сачуване. + Ваше преференце су сачуване. diff --git a/translation/dest/preferences/sv-SE.xml b/translation/dest/preferences/sv-SE.xml index 945cef100860f..a9705ec3cd09f 100644 --- a/translation/dest/preferences/sv-SE.xml +++ b/translation/dest/preferences/sv-SE.xml @@ -51,7 +51,7 @@ Gör drag med din röst Dra pilar för giltiga drag Säg \"Bra parti, väl spelat\" vid förlust eller remi - Dina inställningar har sparats. + Dina inställningar har sparats. Bläddra på tavlan för att spela upp rörelser Daglig e-postavisering som listar dina korrespondensspel Strömmen går igång diff --git a/translation/dest/preferences/ta-IN.xml b/translation/dest/preferences/ta-IN.xml index 8b037fb448f8d..c5c7b1b2a0c47 100644 --- a/translation/dest/preferences/ta-IN.xml +++ b/translation/dest/preferences/ta-IN.xml @@ -8,5 +8,5 @@ ஆட்டக்காரரின் மதிப்பீட்டைக் காட்டு செஸ் கடிகாரம் கூடுதல் நேரம் வழங்கு - உங்கள் விருப்பங்களை சேமிக்கப்பட்டுள்ளது. + உங்கள் விருப்பங்களை சேமிக்கப்பட்டுள்ளது. diff --git a/translation/dest/preferences/th-TH.xml b/translation/dest/preferences/th-TH.xml index 3bfbe4eef68cb..434dda66972e7 100644 --- a/translation/dest/preferences/th-TH.xml +++ b/translation/dest/preferences/th-TH.xml @@ -51,7 +51,7 @@ เดนหมากโดยใข้เสียงของคุณ ลูกศรสแน็ปเพื่อการเดินที่ถูกต้อง ส่งคำว่า \"Good game, well played\" เมี่อแพ้หรือเสมอ - การตั้งค่าของคุณถูกบันทึกแล้ว + การตั้งค่าของคุณถูกบันทึกแล้ว เลื่อนกระดานลงเพื่อดูตาเดินที่ผ่านมา อีเมลรายวันแสดงรายการเกมยาวนานของคุณ สตรีมเมอร์ไลฟ์สด diff --git a/translation/dest/preferences/tk-TM.xml b/translation/dest/preferences/tk-TM.xml index 6dd16aea7b7dc..7473854899530 100644 --- a/translation/dest/preferences/tk-TM.xml +++ b/translation/dest/preferences/tk-TM.xml @@ -36,5 +36,5 @@ Şany 2 göçüm süýşüriň Şany ruha tarap süýşüriň Göçümleri klawiatura bilen düz - Ileri tutmalaryňyz huşda saklandy. + Ileri tutmalaryňyz huşda saklandy. diff --git a/translation/dest/preferences/tl-PH.xml b/translation/dest/preferences/tl-PH.xml index d5bd227032b11..5f1d0936e0dd9 100644 --- a/translation/dest/preferences/tl-PH.xml +++ b/translation/dest/preferences/tl-PH.xml @@ -36,5 +36,5 @@ Galawin ang hari ng dalawang kwadrado Igalaw ang Hari sa rook Maglagay ng mga galaw sa keyboard - Ang iyong mga kagutuhan ay nai-save na. + Ang iyong mga kagutuhan ay nai-save na. diff --git a/translation/dest/preferences/tp-TP.xml b/translation/dest/preferences/tp-TP.xml index 3d131d148fa0e..7a108dc3b9767 100644 --- a/translation/dest/preferences/tp-TP.xml +++ b/translation/dest/preferences/tp-TP.xml @@ -45,7 +45,7 @@ ilo sitelen la sina sitelen e pali mi sitelen e palisa lon supa musi la, o pali ala pali e ni: palisa li nasin tawa tawa ken taso? mi pini ike anu pini pi anpa ala e musi la, o toki \"musi pona, sina pona\" (toki Inli) - wile sina li awen. + wile sina li awen. ilo sike li tawa la o lukin sin e tawa ilo diff --git a/translation/dest/preferences/tr-TR.xml b/translation/dest/preferences/tr-TR.xml index 83c17bba2eda5..a33957d940037 100644 --- a/translation/dest/preferences/tr-TR.xml +++ b/translation/dest/preferences/tr-TR.xml @@ -51,7 +51,7 @@ Hamleleri sesinizle sağlayın Sadece mümkün hamlelere ok çiz Beraberlik veya yenilgiyle biten maçların sonunda \"İyi oyundu, güzel oynadın\" mesajı gönder - Tercihleriniz kaydedildi. + Tercihleriniz kaydedildi. Hamleleri tekrar görmek için fare tekerleğini tahta üzerinde kaydırın Günlük yazışmalı oyunlarınızı listeleyen posta bildirimi alın Canlı yayın başladığında diff --git a/translation/dest/preferences/uk-UA.xml b/translation/dest/preferences/uk-UA.xml index 24e500454b5bf..7a8291bba845f 100644 --- a/translation/dest/preferences/uk-UA.xml +++ b/translation/dest/preferences/uk-UA.xml @@ -51,7 +51,7 @@ Введення ходів за допомогою голосу Стрілки лише для можливих ходів Писати в чат \"Хороша гра, добре зіграно\" після поразки або нічиєї - Ваші налаштування збережено. + Ваші налаштування збережено. Прокрутіть колесом миші на дошці, для того щоб відтворити ходи Щоденне сповіщення на електронну пошту зі списком ваших заочних ігор Стример почав трансляцію diff --git a/translation/dest/preferences/ur-PK.xml b/translation/dest/preferences/ur-PK.xml index a485ba64af9c3..382b35673203f 100644 --- a/translation/dest/preferences/ur-PK.xml +++ b/translation/dest/preferences/ur-PK.xml @@ -40,5 +40,5 @@ بادشاہ کو رخ پر منتقل کریں کی بورڈ کے ذریعے چالیں داخل کریں چالیں اپنی آواز سے داخل کریں - آپ کے اندراج محفوظ کر دیے گئے ہیں + آپ کے اندراج محفوظ کر دیے گئے ہیں diff --git a/translation/dest/preferences/uz-UZ.xml b/translation/dest/preferences/uz-UZ.xml index 4aa03b9f9ab0f..9703a668c9811 100644 --- a/translation/dest/preferences/uz-UZ.xml +++ b/translation/dest/preferences/uz-UZ.xml @@ -47,7 +47,7 @@ Yurish klaviatura orqali amalga oshiriladi Ishonchli yurishlarga oʻqlarni torting Magʻlubiyatga uchraganingizda \"Yaxshi oʻyin, yaxshi oʻnaldi\" deb ayting - Sizning sozlashlaringiz saqlandi. + Sizning sozlashlaringiz saqlandi. Lichess ichidagi qo‘ng‘iroqli xabar Lichessdan tashqarida bo‘lganingizdagi qurilma xabari Brauzer diff --git a/translation/dest/preferences/vi-VN.xml b/translation/dest/preferences/vi-VN.xml index dc800554dd2c6..8128df9b7c181 100644 --- a/translation/dest/preferences/vi-VN.xml +++ b/translation/dest/preferences/vi-VN.xml @@ -1,6 +1,6 @@ - Sửa giao diện + Tuỳ chỉnh Hiển thị Quyền riêng tư Thông báo @@ -51,7 +51,7 @@ Đầu vào nước đi với giọng nói của bạn Tự kéo mũi tên vào ô của nước đi hợp lệ Tự động nhắn \"Good game, well played\" (Ván cờ hay, chơi hay lắm) sau khi hòa hoặc thua - Tùy chọn của bạn đã được lưu + Tùy chọn của bạn đã được lưu Cuộn con chuột trên bàn cờ để xem lại nước đi Email thông báo hàng ngày sẽ bao gồm cả các ván cờ qua thư Streamer đang phát trực tiếp diff --git a/translation/dest/preferences/zh-CN.xml b/translation/dest/preferences/zh-CN.xml index a925739d739c5..a3767d5edbf89 100644 --- a/translation/dest/preferences/zh-CN.xml +++ b/translation/dest/preferences/zh-CN.xml @@ -51,7 +51,7 @@ 用语音输入着法 将箭头吸附到有效着法上 输棋、和棋后自动发送:“厉害,玩得不错!” - 你的设置已保存。 + 你的设置已保存。 在棋盘上滚动鼠标滚轮以回退 每日发送邮件通知你正在进行的通讯棋局 主播开始直播 diff --git a/translation/dest/preferences/zh-TW.xml b/translation/dest/preferences/zh-TW.xml index d092dee1a5713..1472a6e1187c9 100644 --- a/translation/dest/preferences/zh-TW.xml +++ b/translation/dest/preferences/zh-TW.xml @@ -50,7 +50,7 @@ 用語音輸入著法 將右鍵標示箭頭鎖定到合法棋步 輸棋或和棋後自動發送 \"Good game, well played\"。 - 已儲存您的設定。 + 已儲存您的設定。 在騎盤上使用滑鼠滾輪以重新顯示過去棋步 每日以電郵列出您當前的長期對局 追蹤的直播主開始直播 diff --git a/translation/dest/puzzle/he-IL.xml b/translation/dest/puzzle/he-IL.xml index d0b8200974503..18cf16d0285e4 100644 --- a/translation/dest/puzzle/he-IL.xml +++ b/translation/dest/puzzle/he-IL.xml @@ -32,7 +32,7 @@ חידות לפי פתיחות הפתיחות הנפוצות שלך במשחקים מדורגים השתמשו ב״חיפוש בעמוד״ בדפדפן כדי למצוא את הפתיחה המועדפת עליכם! - השתמשו ב-ctrl+F כדי למצוא את הפתיחה המועדפת עליכם! + השתמשו ב־ctrl+F כדי למצוא את הפתיחה המועדפת עליכם! זה לא המהלך! נסו משהו אחר. דירוג: %s diff --git a/translation/dest/puzzleTheme/fa-IR.xml b/translation/dest/puzzleTheme/fa-IR.xml index 0500fcff9d51c..a23be5023a9af 100644 --- a/translation/dest/puzzleTheme/fa-IR.xml +++ b/translation/dest/puzzleTheme/fa-IR.xml @@ -123,5 +123,5 @@ یک ذره از همه چیز. شما نمی دانید چه چیزی پیش روی شماست، بنابراین شما باید برای هر چیزی آماده باشید! دقیقا مثل بازی های واقعی. بازی‌های بازیکن دنبال معماهای ایجادشده از بازی‌های خودتان یا بازی‌های سایر بازیکنان، بگردید. - این معماها به صورت عمومی هستند و می توانند از %s بارگیری شوند. + این معماها به صورت همگانی هستند و می‌توانید از %s بارگیریدشان. diff --git a/translation/dest/puzzleTheme/he-IL.xml b/translation/dest/puzzleTheme/he-IL.xml index 3bfdb138dbca7..0c441b103dbb3 100644 --- a/translation/dest/puzzleTheme/he-IL.xml +++ b/translation/dest/puzzleTheme/he-IL.xml @@ -3,13 +3,13 @@ רגלי מתקדם אחד מהחיילים שלך עמוק בקווי היריב, אולי מאיים להיות מוכתר. יתרון - נצל/י את ההזדמנות כדי להשיג יתרון מכריע. (הערכת יתרון בין 2.0 ל-6.0) + נצל/י את ההזדמנות כדי להשיג יתרון מכריע. (הערכת יתרון בין 2.0 ל־6.0) המט של אנסטסיה פרש, יחד עם צריח או מלכה, לוכדים את המלך היריב בין דופן הלוח לבין כלי מכוחותיו. מט ערבי פרש וצריח לוכדים יחד את המלך היריב בפינת הלוח. תקיפה של ו2 או ו7 - מיקוד איומים על הרגלי ב-ו2 או ו7, כמו למשל בפתיחת \"Fried Liver\". + מיקוד איומים על הרגלי ב־ו2 או ו7, כמו למשל בפתיחת \"Fried Liver\". משיכה החלפת כלים או הקרבה שמעודדות או מכריחות כלי יריב לנדוד למשבצת שמאפשרת טקטיקת המשך. מט שורה אחורית (״מתחת למים״) @@ -29,7 +29,7 @@ מט קוזיו מלכה מבצעת מט למלך סמוך, ששתי משבצות הבריחה שלו חסומות על ידי כלים מכוחותיו. שוויון - חזרו למשחק מעמדת הפסד, והבטיחו תיקו או עמדה מאוזנת. (הערכת יתרון קטנה מ-2.0) + חזרו למשחק מעמדת הפסד, והבטיחו תיקו או עמדה מאוזנת. (הערכת יתרון קטנה מ־2.0) תקיפת צד המלך תקיפת המלך של היריב, לאחר שהצריח לצד המלך. פינוי @@ -107,7 +107,7 @@ מוטיב המשלב כלי חשוב מאוים, זז ומאפשר איום או הכאה של כלי חשוב פחות מאחוריו, ההפך מריתוק. מט חנק מט על ידי פרש בו המלך היריב לא מסוגל לזוז כי הוא מוקף (או חנוק) על ידי כלים מכוחותיו. - משחקי סופר רב-אמנים + משחקי סופר רב־אמנים פאזלים ממשחקים של השחקנים הטובים בעולם. כלי לכוד כלי לא יכול להימנע מהכאה בגלל צמצום מסעים אפשריים. @@ -123,5 +123,5 @@ קצת מהכל. לא תדעו למה לצפות. עליכם להיות מוכנים להכל! בדיוק כמו משחקים אמיתיים. המשחקים שלי חפשו חידות אשר נוצרו ממשחקים שלכם או של שחקנים אחרים. - החידות האלו הן נחלת הכלל, וניתן להוריד אותן מ-%s. + החידות האלו הן נחלת הכלל, וניתן להוריד אותן מ־%s. diff --git a/translation/dest/site/af-ZA.xml b/translation/dest/site/af-ZA.xml index 7d5e2587694c4..cd34be7ec700b 100644 --- a/translation/dest/site/af-ZA.xml +++ b/translation/dest/site/af-ZA.xml @@ -462,9 +462,6 @@ rekenaar analise, kletskamer en deelbare URL te kry. Kies \'n baie veilige naam vir die toernooi. Enige iets ongepaste kan maak dat jou rekening gesluit word. Laat leeg om die toernooi na \'n noemenswaardige speler te vernoem. - Ons stel voor om hierdie uit te los. - As jy die aansluitings vereistes verstel, sal jou toernooi minder spelers hê. - Wys gevorderde instellings Maak die toernooi privaat, en beperk toegang met \'n wagwoord Sluit aan Ontrek @@ -504,8 +501,6 @@ rekenaar analise, kletskamer en deelbare URL te kry. As geen, los oop Profiel Verander profiel - Noem naam - Van Biografie Land of streek Dankie! @@ -548,9 +543,7 @@ rekenaar analise, kletskamer en deelbare URL te kry. Rede Wat makeer? 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. @@ -585,6 +578,7 @@ rekenaar analise, kletskamer en deelbare URL te kry. Stadig In die bord Buite die bord + Al die blokkies van die bord Op stadig spelle Altyd Nooit diff --git a/translation/dest/site/an-ES.xml b/translation/dest/site/an-ES.xml index 8ae5a036de183..485754a139d24 100644 --- a/translation/dest/site/an-ES.xml +++ b/translation/dest/site/an-ES.xml @@ -461,9 +461,6 @@ Tría un nombre muito seguro pa lo torneyo. Cualsequier comportamiento minimament inadecuau podría comportar que se zarre la tuya cuenta. Si lo deixas en blanco, lo torneyo recebirá lo nombre d\'un Gran Mayestro a l\'azar. - Se recomienda deixar como ye. - Si imposas condicions de dentrada, lo torneyo tendrá menos chugadors. - Amostrar configuración abanzada Fer lo torneyo privau y restrinchir l\'acceso con una clau Unir-se Abandonar @@ -503,8 +500,6 @@ Si no aplica, deixa-lo en blanco Perfil Editar perfil - Nombre - Apellido Define lo tuyo estilo Estilo I hai un achuste pa amagar toz es estilos d\'usuario en tot lo puesto web. @@ -550,9 +545,7 @@ Motivo Qué ocurre? Trapa - Insulto Acoso - Manipulación d\'a puntuación Atro Apega lo vinclo a la(s) partida(s) y explica-nos qué i hai de malo en o comportamiento d\'este usuario. No digas simplament \"fa trapazas\"; explica-nos cómo has arribau a esta conclusión. Lo tuyo reporte será procesau mas rapido si ye escrito en anglés. Per favor, da-nos a lo menos un vinclo a una partida en que s\'han feito trapazas. diff --git a/translation/dest/site/ar-SA.xml b/translation/dest/site/ar-SA.xml index d179135d643be..0a37d5238723e 100644 --- a/translation/dest/site/ar-SA.xml +++ b/translation/dest/site/ar-SA.xml @@ -74,8 +74,8 @@ رفع سلسلة الحركات رفع الى التسلسل الرئيسي احذف من هنا - أعلى - وسع التفريع + أعلى + وسع التفريع فرض التسلسل انسخ التفريع بصيغة PGN التقلة @@ -599,9 +599,6 @@ اختر اسماً ملائماً لهذه البطولة. أي شيء غير مناسب ولو حتى قليلاً يمكن أن يعرّض حسابك للإغلاق. أترك الحقل فارغاً وسيتم تسمية البطولة باسم غراند ماستر عشوائي. - نوصي بعدم تغيير هذه الخيارات. - إذا حددت شروطَ دخولٍ للبطولة فقد يكون لديك لاعبون أقل. - إظهار الإعدادات المتقدمة جعل البطولة خاصة، وتقييد الوصول بكلمة مرور إشترك انسحاب @@ -641,8 +638,6 @@ إذا لم يوجد، أتركه فارغًا الملف الشخصي حرر الملف الشخصي - الاسم الأول - اسم العائلة اختيار الشارة الشارة يستخدم هذا الإعداد لإخفاء جميع شارات المستخدمين في الموقع. @@ -693,9 +688,7 @@ السبب ما الأمر؟ غش - إهانة إزعاج - تلاعب بالتقييم أخرى الصق رابط المباراة (المباريات) واشرح بالتفصيل المشكلة في تصرف هذا المستحدم. لا تقل فقط \"انهم يغشون\"، ولكن اشرح لنا سبب استنتاجك. سيكون الرد أسرع إن كتبت بالإنكليزية. برجاء تقديم رابط واحد علي الأقل لمباراة حدث فيها غش. diff --git a/translation/dest/site/av-DA.xml b/translation/dest/site/av-DA.xml index e35a04cf186fe..905602d7db3a3 100644 --- a/translation/dest/site/av-DA.xml +++ b/translation/dest/site/av-DA.xml @@ -459,9 +459,6 @@ Бище турниралъе расги хӀинкъи гьечӀеб цӀар. Бокьараб, дагьаб рекъечӀеб гӀамалалъцин дур аккаунт къаялде бачине бегьула. РакӀалда гьечӀого цо гроссмейстерасул цӀар турниралда лъезе, чӀобого те. - Нижеца гӀакълу кьола гьел хъвагеян. - Дуца турниралде лъугьине шартӀал чӀезаруни, турниралда рукӀаралдаса дагьал чагӀи рукӀине руго. - ЦеретӀурал рекъезариял рихьизе Турнирги къараб гьабе паролалъ гӀорхъиги чӀвай Жувазе Нахъе ине @@ -500,8 +497,6 @@ ГьечӀеб батани, чӀобого те Дур гьумер Дур гьумер хисизе - ЦӀар - Хъизамалъул цӀар ГӀумрухъвай Баркала! Социалиял медиабазул бухьенал @@ -543,9 +538,7 @@ ГӀилла Щиб ккараб? ХӀилла - ХӀакъир гьави Иришгъат гьави - Рейтингалъулъ рекӀкӀ гьаби Цогидаб ХӀаял(з)де бухьен лъе цинги гьев хӀалесул хьвада-чӀвадиялда щиб мекъи бугебали бице. ЦохӀо \"гьес рекӀкӀ гьабунилан\" абуге, дуда кин гьедин ккараб бице. Дур гӀарзаялъухъ жеги хехго балагьила гьеб ингилис мацӀалъ хъван батани. РекӀкӀ гьабураб цо хӀаялдениги бухьен кье. diff --git a/translation/dest/site/az-AZ.xml b/translation/dest/site/az-AZ.xml index aa8b00f750138..407e3b592022f 100644 --- a/translation/dest/site/az-AZ.xml +++ b/translation/dest/site/az-AZ.xml @@ -461,9 +461,6 @@ Turnir üçün çox etibarlı bir ad seçin. Hər hansı uyğunsuz bir ad, hesabınızın bağlanmasına yol aça bilər. Turnirə görkəmli bir şahmatçının adını vermək üçün boş buraxın. - Bunlara toxunmamağınızı məsləhət görürük. - Giriş tələbləri qoyulsa, turnirə daha az oyunçu qoşulacaq. - Ətraflı tənzimləmələr Turniri özəl et və şifrə qoyaraq müraciəti məhdudlaşdır Qoşul Tərk et @@ -502,8 +499,6 @@ Əgər yoxdursa, boş qoyun Profil Profili redaktə et - Ad - Soyad Özü haqqında Təşəkkürlər! Sosial media linkləri @@ -544,9 +539,7 @@ Səbəb Problem nədir? Hiylə - Təhqir Trol - Reytinq manipulyasiyası Digər Oyunun və ya oyunların linkini yapışdırın və bu istifadəçinin davranışında nəyin səhv olduğunu izah edin. Yalnız \"hiylə edirlər\" deməyin, necə bu nəticəyə gəldiyinizi bizə deyin. İngilis dilində yazıldığı təqdirdə hesabat daha sürətli işlənəcəkdir. Lütfən ən azı bir hiyləli oyun linki daxil edin. diff --git a/translation/dest/site/be-BY.xml b/translation/dest/site/be-BY.xml index 7263c6b562e2b..667b640344e45 100644 --- a/translation/dest/site/be-BY.xml +++ b/translation/dest/site/be-BY.xml @@ -513,9 +513,6 @@ Абярыце назву для турніру (бяспечную, калі ласка). Усё, што нават крыху апынецца недарэчным, можа прывесці да блакіроўкі. Пакіньце пустым, каб назваць турнір у гонар выпадковага гросмайстра. - Лепей тут нічога не чапаць. - Калі вы задасце ўмовы ўваходу, то не ўсе гульцы змогуць далучыцца да вашага турніру. - Паказаць дадатковыя налады Зрабіць турнір прыватным і абмяжаваць доступ паролем Далучыцца Адмовіцца ад удзелу @@ -554,8 +551,6 @@ Калі няма, пакіньце пустым Профіль Рэдагаваць профіль - Імя - Прозвішча Біяграфія Краіна або рэгіён Дзякуй! @@ -600,9 +595,7 @@ Прычына Што здарылася? Несумленная гульня - Абраза Троль - Махінацыі з рэйтынгам Іншае Пакіньце ніжэй спасылку на гульню (ці гульні) і патлумачце, што вас непакоіць у паводзінах гэтага карыстальніка. Не пішыце нешта кшталту «ён чмут!» – патлумачце, як вы прыйшлі да гэтага выніку. Мы хутчэй разбярэмся ў сітуацыі, калі вы напішаце нам па-англійску. Калі ласка, дадайце спасылку хаця б на адну гульню, дзе былі парушаны правілы. diff --git a/translation/dest/site/bg-BG.xml b/translation/dest/site/bg-BG.xml index 0aab48b2f21ff..c5247a1059963 100644 --- a/translation/dest/site/bg-BG.xml +++ b/translation/dest/site/bg-BG.xml @@ -70,7 +70,10 @@ Повиши вариацията Направи основна линия Изтриване от тук + Скрий вариациите + Покажи вариациите Покажи като вариация + Копирай PGN на вариацията Ход Вариант на загуба Вариант на победа @@ -463,9 +466,6 @@ Изберете много безопасно име за турнира. Нещо дори и леко неуместно може да доведе до затваряне на вашия акаунт. Ако оставите празно името, тогава турнирът ще бъде кръстен на случайно избран гросмайстор. - Препоръчваме да не пипате тези. - Ако сте задали условия за влизане, вашият турнир ще има по-малко играчи. - Показване на разширените настройки Ограничи достъпа до турнира с парола Включи се Напусни @@ -504,8 +504,7 @@ При липса оставете празно Профил Редактирай профила - Име - Презиме + Истинско име Изберете вашето емоджи Емоджи Животопис @@ -550,9 +549,7 @@ Причина Какъв е случаят? Измама - Обида Вредител - Манипулиране на рейтинга Друго Поставете линк към играта и обяснете какъв е проблемът с поведението на този потребител. Не казвайте единствено, че мами, но ни кажете как сте стигнали до този извод. Вашият доклад ще бъде обработен по-бързо, ако е написан на английски. Моля дай поне един линк до измамна игра. @@ -587,6 +584,7 @@ Бавно На дъската Извън дъската + Всички полета на дъската При бавни игри Винаги Никога @@ -696,6 +694,7 @@ Предходен клон Следващ клон Превключи между предходна/следваща вариация + изиграй избраният ход Нов турнир Шахматният турнир включва различни времеви контроли и варианти Играй в състезания по бърз шахмат! Участвай в разписаните състезания или създай свое собствено. Bullet, Blitz, Classical, Chess960, King of the Hill, Threecheck и още възможности предоставящи безкрайна шахматна забава. @@ -738,6 +737,7 @@ С приятели С всеки Детски режим + Детският режим е включен. В името на безопасността. В детския режим цялата комуникация в сайта е изключена. Включете детския режим, за да защитите вашите деца и ученици от другите потребители. В детския режим логото на Lichess става %s, за да сте сигурни в безопасността на своите деца. Вашата сметка се управлява. Попитайте учителя си по шахмат за вдигане на детския режим. @@ -777,6 +777,12 @@ Прозрачен Тема на устройството URL адрес на фоновия образ: + Дъска + Размер + Прозрачност + Яркост + Цветови тон + Възстановяване на стандартните цветовете Дизайн на фигури Вграждане в уебсайта ви Потребителското име е заето. Опитайте с друго име. diff --git a/translation/dest/site/bn-BD.xml b/translation/dest/site/bn-BD.xml index cd4f8c1b74ac5..c7275b006cbc1 100644 --- a/translation/dest/site/bn-BD.xml +++ b/translation/dest/site/bn-BD.xml @@ -462,9 +462,6 @@ টুর্নামেন্টের জন্য একটি নিরাপদ নাম পছন্দ করুন। এমনকি সামান্য অনুপযুক্ত কিছুও আপনার অ্যাকাউন্ট বন্ধ করতে পারে। কোন গ্র্যান্ডমাস্টারের নামে টুর্নামেন্টের নাম দিতে চাইলে খালি রাখুন। - এটা টাচ না করার জন্য পরামর্শ দেয়া হচ্ছে। - যদি আপনি এন্ট্রি করার শর্ত ঠিক করে দেন, তাহলে টুর্নামেন্টে তুলনামূলকভাবে কম খেলোয়ার পাওয়া যাবে। - এডভান্সড সেটিংসে যান পাসওয়ার্ড দিয়ে টুর্নামেন্টটি গোপনীয় এবং সিমাবদ্ধ করেদিন যোগ দিন উঠিয়ে নিন @@ -504,8 +501,6 @@ যদি কিছুই না হয়, খালি রাখুন পরিচিতি পরিচিতি সম্পাদনা - প্রথম নাম - শেষ নাম আপনার ফ্লেয়ার সেট করুন ফ্লেয়ার পুরো সাইট জুড়ে সমস্ত ব্যবহারকারীর ফ্লেয়ার লুকানোর জন্য একটি সেটিং রয়েছে. @@ -552,9 +547,7 @@ কারণ সম্পূর্ণ ঘটনার বিবরণ দেন চিটিং করছে - অপমান করেছে ব্যঙ্গ করছে - রেটিং ইচ্ছাকৃতভাবে পরিবর্তন অন্য কোনো কারণ এখানে সেই খেলাটির link দেন এবং বলুন ওই ব্যক্তি ব্যবহারে কি অসুবিধা ছিল ? অনুগ্রহ করে একটা চিটেড গেমের লিংক দিন। diff --git a/translation/dest/site/br-FR.xml b/translation/dest/site/br-FR.xml index fed8ba0a12f54..4f939093bf77a 100644 --- a/translation/dest/site/br-FR.xml +++ b/translation/dest/site/br-FR.xml @@ -539,9 +539,6 @@ Choazit un anv dereat-tre evit an tournamant. Un draig ha ne vefe ket dereat a vefe trawalc\'h evit ma serrfemp ho kont. Laoskit goullo evit envel an tournamant diouzh anv ur c\'hoarier echedoù brudet. - Gwelloc\'h eo deoc\'h chom hep kemm anezhe. - Ma c\'houlennit strishadurioù evit mont-tre e vo nebeutoc\'h a c\'hoarierien o kemer perzh en ho tournamant. - Diskouez an arventennoù kempleshoc\'h Gallout a rit krouiñ un tournamant prevez oc\'h ouzhpennañ ur ger-tremen Kemer perzh Dilezel @@ -580,8 +577,6 @@ Ma n\'eus ket, laoskit goullo Profil Aozañ ar profil - Anv - Anv familh Diwar ho penn Trugarez! Liammoù ar mediaoù sokial @@ -624,9 +619,7 @@ Abeg Peseurt kudenn zo? Trucherezh - Kunujenn Troll - Itrikañ renkadur All Pegit liamm ar c\'hrogad(où) ha displegit ar pezh a ya a-dreuz gant emzalc\'h oc\'h enebour. Lâret \"o truchañ emañ\" ne vo ket trawalc\'h, ret eo displegañ mat. Buanoc\'h e pledimp ganti ma skrivit e saozneg. Roit d\'an nebeutañ ul liamm hag a gas d\'ur c\'hrogad trucherezh ennañ. diff --git a/translation/dest/site/bs-BA.xml b/translation/dest/site/bs-BA.xml index 84aee50ac97c7..efb475ac6e1c8 100644 --- a/translation/dest/site/bs-BA.xml +++ b/translation/dest/site/bs-BA.xml @@ -494,9 +494,6 @@ računarsku analizu, mogućnost dopisivanja i link za slanje drugima. Odaberite vrlo siguran naziv za turnir. Sve što je imalo neprikladno može dovesti do trajnog zatvaranja vašeg profila. Ako ostavite prazno, turnir će se nazvati po slučajno odabranom šahovskom igraču. - Preporučujemo da ne dirate ove opcije. - Ako postavite uslov za učestvovanje, vaš turnir će imati manje igrača. - Pogledajte napredne postavke Učinite turnir privatnim i ograničite pristup lozinkom Pridružite se Odustanite @@ -535,8 +532,6 @@ računarsku analizu, mogućnost dopisivanja i link za slanje drugima. Ako nemate rejting, ostavite polje prazno Profil Uredite profil - Ime - Prezime Biografija Hvala! Linkovi na društvene mreže @@ -579,9 +574,7 @@ računarsku analizu, mogućnost dopisivanja i link za slanje drugima. Razlog U čemu je problem? Varanje - Vrijeđanje Provokacija - Manipuliranje rejtingom Ostalo Zalijepite link na partiju ili partije u pitanju i objasnite što nije bilo u redu sa ponašanjem korisnika. Nemojte samo reći \"varao je\", nego objasnite kako ste došli do tog zaključka. Vaša prijava će biti brže obrađena ukoliko je napišete na engleskom jeziku. Molimo navedite barem jedan link na partiju u kojoj je igrač varao. diff --git a/translation/dest/site/ca-ES.xml b/translation/dest/site/ca-ES.xml index 3940f593b8bea..0077c45da2526 100644 --- a/translation/dest/site/ca-ES.xml +++ b/translation/dest/site/ca-ES.xml @@ -70,8 +70,8 @@ Promoure variant Convertir en línia principal Esborrar des d\'aquí - Amagar variacions - Expandir variacions + Amagar variacions + Expandir variacions Forçar variant Copia el PGN de la variació Moviment @@ -467,9 +467,6 @@ Trieu un nom segur per al torneig. Qualsevol comportament mínimament inadequat podria implicar el tancament del teu compte. Deixar en blanc per nombrar el torneig amb un Gran Mestre a l\'atzar. - No recomanem canviar aquestes preferències. - Si s\'estableixen les condicions d\'entrada, el torneig tindrà menys jugadors. - Configuració avançada Fes el torneig privat, i restringeix l\'accés amb clau d\'entrada Inscriu-t\'hi Abandona @@ -509,8 +506,7 @@ Si no en tens, deixa-ho buit Perfil Edita el perfil - Nom - Cognoms + Nom real Defineix el teu estil Icona Existeix una configuració per amagar els estils dels jugadors a tot el lloc web. @@ -557,9 +553,7 @@ Raó Quin és el problema? Trampós - Insult Troll - Manipulació de la puntuació Altres Enganxa l\'enllaç de la partida (o partides) i explica el comportament negatiu d\'aquest usuari. No et limitis a dir que \"fa trampes\", i explica com has arribat a aquesta conclusió. El teu informe serà processat més ràpidament si l\'escrius en anglès. Si us plau, proporcioneu com a mínim un enllaç a un joc on s\'han fet trampes. @@ -594,6 +588,7 @@ Lent Dins del tauler Fora del tauler + Totes les caselles del tauler En els jocs lents Sempre Mai diff --git a/translation/dest/site/ckb-IR.xml b/translation/dest/site/ckb-IR.xml index 31055d425193a..3960e1dd1bcf0 100644 --- a/translation/dest/site/ckb-IR.xml +++ b/translation/dest/site/ckb-IR.xml @@ -465,9 +465,6 @@ ناوێکی گونجاو بۆ پاڵەوانیەتیەکە هەڵبژێرە. هەرشتێک ئەگەر کەمێکیش نەگونجاوبێت، دەبێتە هۆی داخستنی هەژمارەکەت. ئەم خانەیە بە بەتاڵی جێبێڵە، بۆئەوەی بە شێوەیەکی هەڕەمەکی بە ناوی یاریزانێکی دیاری شەترەنجەوە بنرێت. - ئێمە پێشنیار دەکەین، گۆڕانکاری لەو بەشە نەکرێت. - ئەگەر مەرجی چوونەژوورەوە زیادبکەیت ڕەنگە یاریزانی کەمتر بەشداربن. - ڕێکخستنی پێشکەوتوو نیشان بدە پاڵەوانێتییەکە بکە بە تایبەت، و بە وشەی نهێنی چوونە ژوورەوە سنووردار بکە بەشداریبکە کشانەوە @@ -507,8 +504,6 @@ ئەگەر نییە، بە بەتاڵی جێیبێڵە پڕۆفایل دەستکاریکردنی پڕۆفایل - ناو - ناوی باپیرت توانای خۆت دابنێ بەهرە ڕێکخستنێک هەیە بۆ شاردنەوەی هەموو تواناکانی بەکارهێنەر لە سەرانسەری ماڵپەڕەکەدا. @@ -555,9 +550,7 @@ هۆکار کێشەکە چییە? فێڵکردن - بێڕێزی بێزارکەرەکان - دەستکاریکردنی هەڵسەنگاندن ئەوانیتر لینکی یاریەکە لێرە دابنێ، پاشان بە وردی لەبارەی هەڵسوکەوتە نەشیاوەکانی ئەو بەکارهێنەرە بنووسە، هیچ سنورێک بۆ فێڵکردن نییە، ئەمە تەنها نمونەیەکە لێرە بەکاری دێنین، لەبەرئەوەی ئەمە زۆرترین داواکاری ریپۆرتە. تکایە بەلایەنی کەمەوە لینکی یاریەک دابنێ بۆ ئەو یاریانەی فێڵی تێداکراوە. diff --git a/translation/dest/site/co-FR.xml b/translation/dest/site/co-FR.xml index aa05a263c66a1..85e4a36e1e7db 100644 --- a/translation/dest/site/co-FR.xml +++ b/translation/dest/site/co-FR.xml @@ -420,9 +420,6 @@ Scegli un nomu curtesu pè a ghjustra criata. S\'è vo date un nomu sgarbatu à u vostru turneu, u vostru contu pò esse chjosu. Lascià viotu darà à a ghjustra u nomu d\' un ghjucadore famosu. - V\' arricumandemu d\' ùn tuccà. - S\'è vo create parechji cundizioni d\' intrata, ci seranu menu ghjucadori. - Mustrà l\' uzzioni avanzati Creà una ghjustra privata è limità ne l\' accessu cù una parolla d\' intesa Ghjunghje Ritirà si @@ -461,8 +458,6 @@ S\'è ùn n\' avete, lasciate viotu Profilu Edità u prufilu - Nome datu - Cugnome Biugraffia À ringrazià vi! Leie di e rete suciale @@ -504,9 +499,7 @@ Mutivu Chì hè u ? Ingannu - Parullaccia Trollu - Valutazione trafficata Altru Incolla a leia di a partita è spiega ciò ch\' ùn si passa bè cù u cumpurtamentu di u ghjucadore. Ùn scrivi micca soltantu \"inganna\" ma dite dinù perchè. A vostra dumanda serà trattata di modu più prontu s\' ellu hè scrittu in inglese. Pè piace date omancu una leia d\' una partita ingannata. diff --git a/translation/dest/site/cs-CZ.xml b/translation/dest/site/cs-CZ.xml index addb6f204e434..c75eb83398929 100644 --- a/translation/dest/site/cs-CZ.xml +++ b/translation/dest/site/cs-CZ.xml @@ -531,9 +531,6 @@ Vyberte \"bezpečné\" jméno turnaje. Cokoli i mírně nevhodného může způsobit uzavření vašeho účtu. Pokud jméno turnaje nevyplníte, bude pojmenován po náhodném velmistrovi. - Doporučujeme tato nastavení neměnit. - Pokud nastavíte podmínky vstupu, zúčastní se vašeho turnaje méně hráčů. - Zobrazit pokročilá nastavení Udělejte turnaj soukromý a omezte přístup heslem Přidat se Odhlásit se @@ -573,8 +570,6 @@ Pokud nemáte, nechte pole volné Profil Upravit profil - Jméno - Příjmení Nastav si svou ikonu za jménem Upravitelná ikona O mně @@ -621,9 +616,7 @@ Důvod Co se stalo? Podvod - Urážka Troll - Manipulace s ratingem Jiné Vložte link na hru(y) a popište, co je špatně na chování tohoto hráče. (Pokud možno anglicky.) Prosím, uveďte alespoň jeden link na partii, ve které se podvádělo. diff --git a/translation/dest/site/cv-CU.xml b/translation/dest/site/cv-CU.xml index 184cd1218d639..0bec260a5cf0e 100644 --- a/translation/dest/site/cv-CU.xml +++ b/translation/dest/site/cv-CU.xml @@ -283,8 +283,6 @@ Модӑратӑра %s ҫинчен пӗлтер Профӗлӳ Профӗлне тӳрлетни - Йат - Хушамат Биографи Тавах! Lichess TV паҫӑр кӑтартни @@ -318,7 +316,6 @@ Сӑлтав Сӑлтава кӑтарт Ултав - Кӳрентерӳ Троллев Ытти %s тунӑ diff --git a/translation/dest/site/cy-GB.xml b/translation/dest/site/cy-GB.xml index 92615cbdb55ce..d0af00be301c4 100644 --- a/translation/dest/site/cy-GB.xml +++ b/translation/dest/site/cy-GB.xml @@ -336,9 +336,6 @@ Dewisia enw saff i\'r talwrn. Gall unrhywbeth amhriodol arwain at ddileu dy gyfrif. Gadewch yn wag i enwi\'r talwrn ar ol Uwchfeistr Rhyngwladol wedi ei ddewis ar hap. - Rydym yn awgrymu nad ydych yn cyffwrdd rhain. - Bydd gosod telerau ymuno yn debyg o olygu llai o chwaraewyr yn y talwrn. - Dangos gosodiadau cymleth Gwneud y talwrn yn breifat a cyfyngu mynediad gyda cyfrinair Ymuno Ymddiswyddo @@ -375,8 +372,6 @@ Os heb gradd, gadewch yn wag Proffil Golygu proffil - Enw cyntaf - Cyfenw Cofiant Diolch yn fawr! Dolenni cyfryngau cymdeithasol @@ -411,9 +406,7 @@ Rheswm Beth sy\'n bod? Twyll - Sarhad Trôl - Twyllo sgôr Arall Gluda ddolen i\'r gêm ac esbonio be sydd o\'i le am ymddigiad y defnyddiwr hwn. Paid â dweud jyst ´\"Mae o\'n twyllo\", ond dwed pam wyt ti\'n meddwl hynny. Bydd dy adrodd yn cael ei brosesu yn gyflymach os ysgrifenni yn Saesneg. Rhowch o leiaf un dolen at gem lle bu twyllo. diff --git a/translation/dest/site/da-DK.xml b/translation/dest/site/da-DK.xml index 3e0f9b7520432..2388452c2a978 100644 --- a/translation/dest/site/da-DK.xml +++ b/translation/dest/site/da-DK.xml @@ -70,8 +70,8 @@ Forfrem variant Gør til hovedlinjen Slet herfra - Fold variationer sammen - Udvid variationer + Fold variationer sammen + Udvid variationer Gennemtving variation Kopiér variant-PGN Træk @@ -467,9 +467,6 @@ Vælg et meget ufarligt navn til turneringen. Selv noget lidt upassende kan resultere i at din konto lukkes. Lad stå tomt for at navngive turneringen efter en tilfældig stormester. - Vi anbefaler at ikke ændre disse. - Hvis du stiller betingelser for deltagelse, vil din turnering have færre spillere. - Vis avancerede Indstillinger Gør turneringen privat og begræns adgang med en adgangskode Deltag Frameld @@ -509,8 +506,7 @@ Hvis ingen, lad den være tom Profil Redigér profil - Fornavn - Efternavn + Rigtige navn Indstil dit ikon Ikon Der er en indstilling til at skjule alle brugerikoner på tværs af hele webstedet. @@ -557,9 +553,7 @@ Årsag Hvad drejer henvendelsen sig om? Snyd - Fornærmelse Troll - Manipulation af rating Andet Indsæt et link til partiet (eller partierne) og forklar hvad der er i vejen med brugerens opførsel. Angiv mindst ét link til et parti med snyd. @@ -594,6 +588,7 @@ Langsom På brættet Uden for brættet + Alle felter på brættet I langsomme spil Altid Aldrig diff --git a/translation/dest/site/de-DE.xml b/translation/dest/site/de-DE.xml index ae4a17e4c7e0c..3a8cb76b96724 100644 --- a/translation/dest/site/de-DE.xml +++ b/translation/dest/site/de-DE.xml @@ -467,9 +467,6 @@ Wähle einen äußerst sicheren Namen für das Turnier. Sämtliche unangemessene Inhalte können zur Schließung deines Benutzerkontos führen. Frei lassen, um das Turnier nach einem zufälligen Großmeister zu benennen. - Wir empfehlen, diese nicht zu ändern. - Falls du Teilnahmebedingungen setzt, wird das Turnier weniger Spieler haben. - Zeige erweiterte Einstellungen Stelle das Turnier auf privat und beschränke den Zugang durch ein Passwort Teilnehmen Verlassen @@ -509,8 +506,7 @@ Nur falls vorhanden Profil Profil bearbeiten - Vorname - Nachname + Echter Name Setze dein Flair Flair Es existiert eine Einstellungsmöglichkeit, um alle Benutzerflairs auf der gesamten Seite zu verbergen. @@ -557,9 +553,7 @@ Grund Was ist das Problem? Betrug - Beleidigung Troll - Manipulation der Wertungszahl Sonstiges Füge den Link zu einer oder mehreren Partien ein und erkläre die Auffälligkeiten bezüglich des Spielerverhaltens. Bitte schreibe nicht einfach nur „dieser Spieler betrügt“, sondern begründe auch, wie Du zu diesem Schluss kommst. Dein Bericht wird schneller bearbeitet, wenn er in englischer Sprache verfasst ist. Bitte gib mindestens einen Link zu einem Spiel an, in dem betrogen wurde. @@ -594,6 +588,7 @@ Langsam Auf dem Brett Neben dem Brett + Alle Felder auf dem Brett Bei langsamen Spielen Immer Nie diff --git a/translation/dest/site/el-GR.xml b/translation/dest/site/el-GR.xml index 487f92afc8a5c..816aa797b5c16 100644 --- a/translation/dest/site/el-GR.xml +++ b/translation/dest/site/el-GR.xml @@ -466,9 +466,6 @@ Διαλέξτε ένα πολύ ασφαλές όνομα για το τουρνουά. Οτιδήποτε ακόμα και ελάχιστα ακατάλληλο μπορεί να κλείσει τον λογαριασμό σας. Αφήστε το κενό για να πάρει το όνομά του τυχαία από κάποιον γνωστό σκακιστή. - Σας συνιστούμε να μην τα αλλάξετε αυτά. - Εάν ορίσετε προϋποθέσεις εισόδου, το τουρνουά σας θα έχει λιγότερους παίκτες. - Εμφάνιση ρυθμίσεων για προχωρημένους Κάντε το τουρνουά ιδιωτικό, και περιορίστε την πρόσβαση με κωδικό Συμμετοχή Απόσυρση @@ -508,8 +505,6 @@ Αν δεν υπάρχει, αφήστε κενό Προφίλ Επεξεργασία προφίλ - Όνομα - Επώνυμο Ορίστε τη νιφάδα σας Νιφάδα Υπάρχει μια ρύθμιση για να κρύψει όλες τις νιφάδες χρήστη σε ολόκληρη την ιστοσελίδα. @@ -556,9 +551,7 @@ Αιτία Τι τρέχει; Απάτη - Προσβολή Εμπαιγμός - Παραποίηση βαθμολογίας Άλλο Κάντε επικόλληση τον σύνδεσμο για το παιχνίδι(α) και εξηγήστε τι είναι παράξενο στη συμπεριφορά του χρήστη. Μην πείτε απλά «επειδή κλέβει», πείτε μας πως καταλήξατε σε αυτό το συμπέρασμα. Η αναφορά σας θα επεξεργαστεί πιο γρήγορα αν είναι γραμμένη στα αγγλικά. Καταχωρίστε τουλάχιστον έναν σύνδεσμο σε ένα παιχνίδι εξαπάτησης. @@ -593,6 +586,7 @@ Αργή Μέσα στη σκακιέρα Εκτός της σκακιέρας + Σε όλα τα τετράγωνα της σκακιέρας Σε αργά παιχνίδια Πάντα Ποτέ diff --git a/translation/dest/site/en-US.xml b/translation/dest/site/en-US.xml index 332903fbaaaaa..c90a96949a5c7 100644 --- a/translation/dest/site/en-US.xml +++ b/translation/dest/site/en-US.xml @@ -468,9 +468,6 @@ computer analysis, game chat and shareable URL. Pick a very safe name for the tournament. Anything even slightly inappropriate could get your account closed. Leave empty to name the tournament after a notable chess player. - We recommend not touching these. - If you set entry requirements, your tournament will have fewer players. - Show advanced settings Make the tournament private, and restrict access with a password Join Withdraw @@ -510,8 +507,7 @@ computer analysis, game chat and shareable URL. If none, leave empty Profile Edit profile - First name - Last name + Real name Set your flair Flair There is a setting to hide all user flairs across the entire site. @@ -558,9 +554,7 @@ computer analysis, game chat and shareable URL. Reason What\'s the matter? Cheat - Insult Troll - Rating manipulation Other Paste the link to the game(s) and explain what is wrong about this user behavior. Don\'t just say \"they cheat\", but tell us how you came to this conclusion. Your report will be processed faster if written in English. Please provide at least one link to a cheated game. @@ -595,6 +589,7 @@ computer analysis, game chat and shareable URL. Slow Inside the board Outside the board + All squares on the board On slow games Always Never diff --git a/translation/dest/site/eo-UY.xml b/translation/dest/site/eo-UY.xml index 8719df99223f5..b471a8e2e1e60 100644 --- a/translation/dest/site/eo-UY.xml +++ b/translation/dest/site/eo-UY.xml @@ -464,9 +464,6 @@ Elekti tre sekuran nomon por la turniro. Se vi uzas maltaŭgan aŭ maldecan nomon, ni fermos vian konton. Se vi lasas ĝin malplena, ni uzos la nomon de fama ŝakludisto. - Ni rekomendi ke vi ne tuŝas ĉi tiujn. - Se vi aldonas aliĝpostulojn, via turniro havos malpli da ludantoj. - Montri spertulajn agordojn Faru la turniron privata kaj restriktu la aliron per pasvorto Aliĝi Eliri @@ -506,8 +503,6 @@ Se neniu, lasu malplena Profilo Redakti profilon - Persona nomo - Familia nomo Agordi viaj emoĝio Emoĝio Estas agordo por kaŝi ĉiujn uzantajn emoĝiojn tra la tuta retejo. @@ -553,9 +548,7 @@ Kialo Pri kio temas la raporto? Trompo - Insulto Trolo - Manipulado de rango Io alia Inkluzivu la ligilon al la ludo(j) kaj ekspliku tion, kio malbonas pri la konduto de ĉi tiu uzanto. Bonvolu doni almenaŭ unu ligilon al ludo en kiu oni friponis. diff --git a/translation/dest/site/es-ES.xml b/translation/dest/site/es-ES.xml index c1e3946adc929..ebadd95e42b34 100644 --- a/translation/dest/site/es-ES.xml +++ b/translation/dest/site/es-ES.xml @@ -467,9 +467,6 @@ Escoge un nombre muy seguro para el torneo. Cualquier comportamiento mínimamente inapropiado podría conllevar al cierre de tu cuenta. Déjalo en blanco para poner al torneo el nombre de un Gran Maestro al azar. - Se recomienda dejar como está. - Si impones condiciones de entrada, el torneo tendrá menos jugadores. - Mostrar configuración avanzada Hacer el torneo privado y restringir el acceso con una contraseña Unirse Abandonar @@ -509,8 +506,7 @@ Si no aplica, déjalo en blanco Perfil Editar perfil - Nombre - Apellido + Nombre real Configura tu entorno Entorno Existe una opción para ocultar la configuración de entorno en todo el sitio. @@ -557,9 +553,7 @@ Motivo ¿Qué ocurre? Trampa - Insulto Acoso - Manipulación de puntuación Otro Pega el enlace a la(s) partida(s) y explícanos qué hay de malo en el comportamiento de este usuario. No digas simplemente \"hace trampa\"; explícanos cómo has llegado a esta conclusión. Tu informe será procesado más rápido si está escrito en inglés. Por favor, proporciona al menos un enlace a una partida en la que se hicieron trampas. @@ -594,6 +588,7 @@ Lento Dentro del tablero Fuera del tablero + Todas las casillas del tablero En partidas lentas Siempre Nunca diff --git a/translation/dest/site/et-EE.xml b/translation/dest/site/et-EE.xml index 2a4e5a45e07a9..47fd407698a1c 100644 --- a/translation/dest/site/et-EE.xml +++ b/translation/dest/site/et-EE.xml @@ -446,9 +446,6 @@ arvutianalüüsi, mängu jututoa ning jagatava URL-i. Vali turniirile sobiv nimi. Isegi pisut kohatu nimi võib tähendada sinu konto sulgemist. Jäta tühjaks, et nimetada turniir juhusliku suurmeistri järgi. - Me ei soovita neid muuta. - Kui sa lisad osalemiseks tingimusi, siis osaleb turniiril vähem inimesi. - Näita täpsemaid sätteid Tee turniir privaatseks ja piira juurdepääsu parooliga Liitu Loobu @@ -487,8 +484,6 @@ arvutianalüüsi, mängu jututoa ning jagatava URL-i. Kui pole, jäta tühjaks Profiil Muuda profiili - Eesnimi - Perekonnanimi Kirjeldus Aitäh! Sotsiaalmeedia lingid @@ -530,9 +525,7 @@ arvutianalüüsi, mängu jututoa ning jagatava URL-i. Põhjus Milles asi? Sohk - Solvang Troll - Reitingu manipulatsioon Muu Kleebi link mängu(de)st ja selgita, mis on valesti selle kasutaja käitumises. Ära ütle lihtsalt \"ta teeb sohki\", vaid seleta, kuidas sa selle järelduseni jõudsid. Sõnum käsitletakse kiiremini kui see on kirjutatud inglise keeles. Palun andke vähemalt üks link pettust sisaldavale mängule. diff --git a/translation/dest/site/eu-ES.xml b/translation/dest/site/eu-ES.xml index e2f08bc85484e..90e0fbcc10424 100644 --- a/translation/dest/site/eu-ES.xml +++ b/translation/dest/site/eu-ES.xml @@ -464,9 +464,6 @@ Adeitasunezko izena hauta ezazu. Errespetua galduz gero, zure kontua itxiko dugu. Ez baduzu betetzen, txapelketak Maisu Handi baten izena hartuko du, ausaz. - Hobe ez ukitu hau. - Parte hartzeko baldintzak jartzen badituzu, jokalari gutxiago sartuko dira txapelketan. - Ezarpen aurreratuak Txapelketa pribatu egin eta sarrera pasahitzarekin babestu Sartu Txapelketa utzi @@ -506,8 +503,6 @@ Ez baduzu, hutsik utzi Profila Nire profila editatu - Izena - Abizena Ezarri zure iruditxoa Iruditxoa Webgune guztian zehar erabiltzaile guztien iruditxoak ezkutatzeko ezarpen bat dago. @@ -553,9 +548,7 @@ Arrazoia Zein da arazoa? Tranpak - Irainak Trolla - Puntuazioa manipulatu Bestelakoak Partidaren esteka itsasi, eta azaldu zer egin duen gaizki erabiltzaileak. Ez esan \"tranpak egiten ditu\" bakarrik, eman horren arrazoiak. Zure mezua azkarrago begiratuko dugu ingelesez idazten baduzu. Iruzurra izandako partida baten lotura bidali gutxienez. diff --git a/translation/dest/site/fa-IR.xml b/translation/dest/site/fa-IR.xml index f4d20c9beedd6..a48bf958de11e 100644 --- a/translation/dest/site/fa-IR.xml +++ b/translation/dest/site/fa-IR.xml @@ -44,7 +44,7 @@ ممکن است حریف شما بازی را ترک کرده باشد. شما می توانید ادعای پیروزی کنید, اعلام تساوی کنید یا منتظر او بمانید. ادعای پیروزی اعلام تساوی - لطفا در گپ زدن مودب باشید! + لطفا در گپ‌زنی بااَدب باشید! نخستین کسی که به این وب‌نشانی آید با شما بازی خواهد کرد. سفید تسلیم شد سیاه تسلیم شد @@ -112,7 +112,7 @@ همه چیز آماده است! PGN را وارد کنید حذف - آیا این بازیِ وارد شده حذف گردد؟ + آیا این بازیِ درونبُرده پاک شود؟ حالت پخش مشابه بازی درنگ حین اشتباهات @@ -142,7 +142,7 @@ %s غیردقیق %s غیردقیق - مدت زمان حركت + مدت حركت‌ها چرخاندن صفحه تکرار سه گانه ادعای تساوی @@ -325,7 +325,7 @@ %s بازی در حال انجام %s بازی در حال انجام - استخراج بازی ها + برون‏بُرد بازی‌ها محدوده درجه‌بندی %s ثانیه اضافه کن @@ -398,7 +398,7 @@ ذخیره جدول رده‌بندی از وضعیت فعلی نماگرفت بگیرید - دانلود گیف بازی + بارگیری GIF بازی پوزیشن دلخواه(FEN) را در این قسمت وارد کنید متن PGN را در این قسمت وارد کنید یا یک فایل PGN بارگذاری کنید @@ -467,9 +467,6 @@ یک نام بسیار امن برای مسابقات انتخاب کنید. هرچیز حتی کمی نامناسب ممکن است باعث بسته شدن حساب کاربری شما بشود. این مکان را خالی بگذارید تا به صورت تصادفی اسم یک استاد بزرگ برای مسابقات انتخاب شود. - توصیه میکنیم که تنظیمات پیشرفته را تغییر ندهید. - اگر برای مسابقات شرایط ورود تعیین کنید بازیکنان کمتری وارد می شوند. - نمایش تنظیمات پیشرفته تورنومنت را به حالت خصوصی در بیاورید و دسترسی را محدود به داشتن پسورد کنید ملحق شدن منصرف شدن @@ -504,13 +501,12 @@ بارگذاری موقعیت خصوصی گزارش %s به مدیران سایت - میزان تکمیل نمایه: %s + میزان تکمیل رُخ‌نما: %s درجه‌‏بندی %s اگر ندارید، خالی گذارید - نمایه - ویرایش نمایه - نام - نام خانوادگی + رُخ‌نما + ویرایش رُخ‌نما + نام راستین تعیین کردن شکلک نشان تنظیماتی برای مخفی کردن همه شکلک‌های کاربر در کل ویگاه وجود دارد. @@ -557,9 +553,7 @@ دلیل موضوع تقلب - توهین کردن ترول - دستکاری درجه‌بندی موضوعات دیگر لینک بازی های این کاربر را قرار دهید و توضیع دهید خطای رفتار این بازیکن چه بوده است لطفآ حداقل یک نمونه تقلب در بازی را مطرح کنید. @@ -594,6 +588,7 @@ آرام در صفحه بیرون صفحه + همه خانه‌های صفحه در بازی های آرام همیشه هرگز @@ -655,7 +650,7 @@ نگاه کردن فیلم ها بَرخَط-محتواسازها - برنامه ی موبایل + گوشی‌افزار وبداران درباره ما درباره %s @@ -740,8 +735,8 @@ تاخیر شبکه بین شما و Lichess زمان سپری شده برای پردازش یک حرکت بارگیری حرکت‌نویسی - دانلود خام - دانلود جایگذاری شده + بارگیری خام + بارگیری درونبُرد رودررو شما می توانید برای حرکت در بازی از صفحه استفاده کنید برای مشاهده آن ها اسکرول کنید. @@ -763,7 +758,7 @@ جلسات باطل کردن تمامی موارد همه جا شطرنج بازی کنید - رایگان به مانند لیچس + کاملا رایگان ساخته شده با عشق به شطرنج نه پول همگی از مزایا بصورت رایگان استفاده می کنند بدون تبلیغات @@ -771,12 +766,12 @@ موبایل و تبلت گلوله‌ای، برق‌آسا، مرسوم شطرنج مکاتبه ای - بازی کردن بَرخط و بُرون‌خط + بازی بَرخط و بُرون‌خط دیدن راهِ حل دنبال کردن و پیشنهاد بازی دادن به دوستان - در %s زبان‌ها موجود است - در %s زبان‌ها موجود است + در %s زبان موجود است! + در %s زبان موجود است! تجزیه و تحلیلِ بازی %1$s میزبان ها %2$s @@ -800,7 +795,7 @@ فام بازنشاندن به رنگ‌های پیش‌فرض نوع مهره - قرار دادن در سایت خود + قرار دادن در وبگاه خود این نام کاربری در حال حاضر انتخاب شده است.لطفا نام دیگری انتخاب کنید. نام کاربری باید با حرف شروع شود. نام کاربری باید با حرف یا شماره خاتمه یابد. diff --git a/translation/dest/site/fi-FI.xml b/translation/dest/site/fi-FI.xml index 62eaa8ba665cc..8db32305753fc 100644 --- a/translation/dest/site/fi-FI.xml +++ b/translation/dest/site/fi-FI.xml @@ -70,8 +70,8 @@ Nosta muunnelmaa Päämuunnelmaksi Poista tästä alkaen - Taita kokoon muunnelmat - Laajenna muunnelmat + Taita kokoon muunnelmat + Laajenna muunnelmat Pakota muunnelmaksi Kopioi muunnelman PGN Siirto @@ -467,9 +467,6 @@ Valitse asiallinen nimi turnaukselle. Hiemankin epäasiallinen nimi voi johtaa käyttäjätunnuksesi sulkemiseen. Jos jätät tyhjäksi, turnaus nimetään jonkun suurmestarin mukaan. - Emme suosittele näihin koskemista. - Jos asetat liittymisehtoja, turnauksessasi tulee olemaan vähemmän pelaajia. - Näytä lisäasetukset Tee turnauksesta yksityinen ja aseta salasana, jolla siihen pääsee Liity Peru osallistuminen @@ -509,8 +506,7 @@ Jätä tyhjäksi jos ei ole Profiili Muokkaa profiilia - Etunimi - Sukunimi + Oikea nimi Valitse tyylisi Tyyli On olemassa asetus, jolla voit piilottaa kaikkien käyttäjien tyylit koko sivustolla. @@ -557,9 +553,7 @@ Syy Mikä hätänä? Huijaus - Loukkaus Trolli - Vahvuusluvun manipulointi Muu Liitä linkki peliin/peleihin ja kerro, mikä on pielessä tämän käyttäjän käytöksessä. Älä vain sano että \"hän huijaa\", vaan kerro meille miksi ajattelet näin. Raporttisi käydään läpi nopeammin, jos se on kirjoitettu englanniksi. Anna ainakin yksi linkki peliin, jossa epäilet huijaamista. @@ -594,6 +588,7 @@ Hidas Laudan sisäpuolella Laudan ulkopuolella + Kaikki laudan ruudut Hitaissa peleissä Aina Ei koskaan diff --git a/translation/dest/site/fo-FO.xml b/translation/dest/site/fo-FO.xml index 48fe10bde8aab..f0b0d01874888 100644 --- a/translation/dest/site/fo-FO.xml +++ b/translation/dest/site/fo-FO.xml @@ -410,9 +410,6 @@ Vel eitt trygt og hóskiligt navn til kappingina. Er navnið á nakran hátt ósømiligt, kann konta tín verða stongd. Lat teigin vera tóman, um tú vilt, at kappingin verður nevnd eftir gitnum telvara. - Vit viðmæla ikki at nerta hesar. - Um tú setir luttøkutreytir, verða færri telvarar í kappingini. - Vís framkomnar stillingar Ger so kappingin ikki er almen, og avmarka atgongdina við einum loyniorði Tak lut Far úr @@ -449,8 +446,6 @@ Um einki, lat teigin vera tóman Vangamynd Broyt vangamynd - Fornavn - Eftirnavn Ævilýsing Takk fyri! Leinki til sosialar miðlar @@ -490,9 +485,7 @@ Orsøk Hvat bagir? Snýt - Háðar Trøll - Svik við styrkitali Annað Flyt leinkið til talvið ella talvini higar, og greið frá, hvat bagir atburðinum hjá brúkaranum. Skriva ikki bert \"hann snýtir\", men sig okkum, hvussu tú komst til hesa niðurstøðu. Fráboðan tín verður skjótari viðgjørd, um hon verður skrivað á enskum. Útvega leinki til í minsta lagi eitt talv, har snýtt varð. diff --git a/translation/dest/site/fr-FR.xml b/translation/dest/site/fr-FR.xml index a53b18564860f..72e51aa92c751 100644 --- a/translation/dest/site/fr-FR.xml +++ b/translation/dest/site/fr-FR.xml @@ -70,8 +70,8 @@ Promouvoir la variante En faire la variante principale Supprimer à partir d\'ici - Fermer les variantes - Afficher les variantes + Fermer les variantes + Afficher les variantes Forcer la variante Copier le PGN de la variante Coup @@ -467,9 +467,6 @@ Choisissez un sage nom pour le tournoi. Toute conduite inappropriée aboutira à la fermeture de votre compte. Laissez vide pour que le tournoi soit nommé d\'après le nom d\'un grand maître. - Nous recommandons de ne pas modifier cela. - Si vous définissez des conditions d\'admission, votre tournoi aura moins de joueurs. - Afficher les paramètres avancés Rendre le tournoi privé et en restreindre l\'accès avec un mot de passe Rejoindre Renoncer @@ -509,8 +506,7 @@ Sinon, laissez vide Profil Modifier le profil - Prénom - Nom + Nom réel Choisir votre émoji Émoji Un paramètre permet de cacher les émojis des utilisateurs sur tout le site. @@ -557,9 +553,7 @@ Motif Quel est le problème ? Triche - Insulte Troll - Manipulation du classement Autre Copiez le(s) lien(s) vers les parties et expliquez en quoi le comportement de cet utilisateur est inapproprié. Ne dites pas juste \"il triche\", mais expliquez comment vous êtes arrivé à cette conclusion. Votre rapport sera traité plus vite s\'il est écrit en anglais. Merci de fournir au moins un lien vers une partie où il y a eu triche. @@ -594,6 +588,7 @@ Lente Sur l\'échiquier En dehors de l\'échiquier + Toutes les cases Durant les parties lentes Toujours Jamais diff --git a/translation/dest/site/fy-NL.xml b/translation/dest/site/fy-NL.xml index a8ec219a1c671..c57226aa4d797 100644 --- a/translation/dest/site/fy-NL.xml +++ b/translation/dest/site/fy-NL.xml @@ -356,9 +356,6 @@ kompjûteranalyze, partijpetear en dielbere URL te krijen. Kies in feilige, net kontroversjele namme foar it toernoai. Sels by it fermoeden fan in mispleatste namme, kin jo account sluten wurde. Lit leech om it toernoai nei in opmerklike skaakspiler te ferneamen. - Wy riede oan dizze ynstellingen sa te litten. - As jo tagongseasken ynstelle, sil jo toernoai minder spilers hawwe. - Toan avansearre ynstellingen Meitsje it toernoai privee, en behein de tagong mei in wachtwurd Meidwaan Weromlûke @@ -393,8 +390,6 @@ kompjûteranalyze, partijpetear en dielbere URL te krijen. As der gjin is, lit dan leech Profyl Profyl bewurkje - Foarnamme - Efternamme Biografy Tankewol! Keppelingen nei sosjale media @@ -427,7 +422,6 @@ kompjûteranalyze, partijpetear en dielbere URL te krijen. Reden Wat is der oan de hân? Falsk spul - Belediging Provokaasje Oars Plak de link nei it spul(len) en lis út wat ferkeard is oan it gedrach fan dizze brûker. Sis net \"dit is falsk spul\", mar lis ús út hoe ast ta dizze konklúzje bist kaam. Dyn rapport sil rapper wurde ferwurke as it yn it Ingelsk wurdt skreaun. diff --git a/translation/dest/site/ga-IE.xml b/translation/dest/site/ga-IE.xml index f4d98f4466878..b76a6c9361ab2 100644 --- a/translation/dest/site/ga-IE.xml +++ b/translation/dest/site/ga-IE.xml @@ -543,9 +543,6 @@ anailís ríomhaire, comhrá cluiche agus URL inroinnte. Pioc ainm an-sábháilte don chomórtas. D\'fhéadfadh do chuntas a dhúnadh má roghnaítear aon rud míchuí. Fág folamh chun an comórtas a ainmniú i ndiaidh imreoir fichille suntasach. - Molaimid iad seo a fhágáil mar atá. - Má shocraíonn tú riachtanais iontrála don chomórtas, beidh níos lú imreoirí i do chomórtas. - Taispeáin ardsocruithe Déan an comórtas príobháideach, agus cuir srian ar rochtain le pasfhocal Cláraigh Tarraing siar @@ -584,8 +581,6 @@ anailís ríomhaire, comhrá cluiche agus URL inroinnte. Mura bhfuil, fág folamh Próifíl Cuir próifíl in eagar - Ainm - Sloinne Beathaisnéis Go raibh maith agat! Naisc chuig na meáin shóisialta @@ -630,9 +625,7 @@ anailís ríomhaire, comhrá cluiche agus URL inroinnte. Fáth Cad atá cearr? Caimiléir - Masla Troll - Ionramháil rátála Eile Greamaigh an nasc chuig an gcluiche/na cluichí agus mínigh cad atá cearr le hiompar an úsáideora. Ná habair go díreach go mbíonn \"caimiléireacht\" ar bun acu, ach inis dúinn faoin dóigh a fuair tú amach faoi. Faraor, déanfar do thuairisc a phróiseáil níos tapúla más i mBéarla atá sé. Cuir nasc ar fáil chuig cluiche amháin ar a laghad ar tharla caimiléireacht ann le do thoil. diff --git a/translation/dest/site/gd-GB.xml b/translation/dest/site/gd-GB.xml index 248d92cd37860..ecc8650febbc5 100644 --- a/translation/dest/site/gd-GB.xml +++ b/translation/dest/site/gd-GB.xml @@ -191,8 +191,6 @@ Cuir gearan mu %s dha na maoir Pròifil Deasaich a’ phròifil - Ainm - Sloinneadh Eachdraidh-beatha Mòran taing! Air tbh Lichess roimhe @@ -222,7 +220,6 @@ Adhbhar Dè an trioblaid? Cealgaireachd - Mosach Troll Adhbhar eile le %s diff --git a/translation/dest/site/gl-ES.xml b/translation/dest/site/gl-ES.xml index 2a36432053d19..5cff8daf187a0 100644 --- a/translation/dest/site/gl-ES.xml +++ b/translation/dest/site/gl-ES.xml @@ -467,9 +467,6 @@ Escolle un nome seguro para o torneo. Calquera comportamento minimamente inadecuado podería levar ao peche da túa conta. Deixar en branco para poñerlle ó torneo o nome dun Grande Mestre notable. - Non recomendamos cambiar estes axustes. - Se estableces condicións de entrada, o teu torneo terá menos xogadores. - Amosar axustes avanzados Fai que o torneo sexa privado e restrinxe o acceso cun contrasinal Unirse Retirarse @@ -509,8 +506,7 @@ Se non aplica, déixao en branco Perfil Editar perfil - Nome - Apelido(s) + Nome real Escolle a túa habelencia Habelencia Nas preferencias podes agochar por completo as habelencias dos xogadores en todo o sitio. @@ -557,9 +553,7 @@ Motivo Que pasou? Trampa - Insulto Troll - Manipulación da puntuación Outro Pega a ligazón á(s) partida(s) e explica o que é incorrecto no comportamento deste usuario. Non digas só \"fai trampas\", cóntanos como chegaches a esa conclusión. A túa denuncia será procesada máis rapidamente se está escrita en inglés. Por favor, incorpora cando menos unha ligazón a unha partida na que se fixeron trampas. @@ -594,6 +588,7 @@ Lenta Dentro do taboleiro Fóra do taboleiro + Todas as casas do taboleiro En partidas lentas Sempre Nunca diff --git a/translation/dest/site/gsw-CH.xml b/translation/dest/site/gsw-CH.xml index 7c442c77447cf..e1f70373ffad5 100644 --- a/translation/dest/site/gsw-CH.xml +++ b/translation/dest/site/gsw-CH.xml @@ -469,9 +469,6 @@ zum bewise dass du en Mänsch bisch. Wähl en möglichscht sichere Name fürs Turnier. Alles - au liecht Unagmässes - cha zur Schlüssig vu dim Konto fühere. Frei la zum s\'Turnier nach eme namhafte Schachschpiller z\'benänne. - Mir empfehled, das nöd z\'ändere. - Wänn du Teilnahmebedingige verlangsch, wird dis Turnier weniger Schpiller ha. - Zeig die erwiterete Ischtellige Mach das Turnier privat und beschränk de Zuegang mit Passwort Mach mit Usstige @@ -511,8 +508,7 @@ zum bewise dass du en Mänsch bisch. Nur wänn vorhande Profil Profil bearbeite - Vorname - Nachname + Richtige Name Wähl dis Emoji Emoji Alli Benutzer-Emojis chönnd - uf de ganze Site - usbländet werde. @@ -559,9 +555,7 @@ zum bewise dass du en Mänsch bisch. Grund Was isch s\'Problem? Bschiss - Beleidigung Troll - Manipulation vu de Wertig Suschtigs Füeg de Link dere/dene Partie bi und erchlär, wie sich de Benutzer falsch benah hät. Säg nöd nur \"de bschisst\", schrib eus wie du da druf chunnsch. (änglisch gschribeni Mäldige, werded schnäller behandlet). Bitte gib mindeschtens 1 Link zume Schpiel a, wo bschisse worde isch. @@ -596,6 +590,7 @@ zum bewise dass du en Mänsch bisch. Langsam Uf em Brätt Usse vum Brätt + Uf jedem Fäld Bi langsame Partie Immer Nie diff --git a/translation/dest/site/gu-IN.xml b/translation/dest/site/gu-IN.xml index 0034d61fe2b74..e0152a6c23e01 100644 --- a/translation/dest/site/gu-IN.xml +++ b/translation/dest/site/gu-IN.xml @@ -409,8 +409,6 @@ કાળા ની જીત ડ્રો પ્રોફાઇલ સંપાદિત કરો - પ્રથમ નામ - અટક તમારો સ્વભાવ સેટ કરો સ્વભાવ અન્યથા દેખાવ આખી સાઈટ પર તમામ યુઝર ફ્લેર્સને છુપાવવા માટે એક સેટિંગ છે. @@ -453,9 +451,7 @@ કારણ શું બાબત છે? છેતરપિંડી - અપમાન મજાક - રેટિંગ છેતરવું અન્ય રમત(ઓ) ની લિંક પેસ્ટ કરો અને આ વપરાશકર્તાના વર્તન વિશે શું ખોટું છે તે સમજાવો. ફક્ત \"તેઓ છેતરે છે\" એમ ન બોલો, પરંતુ તમે આ નિષ્કર્ષ પર કેવી રીતે આવ્યા તે અમને કહો. જો અંગ્રેજીમાં લખવામાં આવે તો તમારી રિપોર્ટ પર ઝડપથી પ્રક્રિયા કરવામાં આવશે. કૃપા કરીને છેતરાયેલી રમતની ઓછામાં ઓછી એક લિંક પ્રદાન કરો. diff --git a/translation/dest/site/he-IL.xml b/translation/dest/site/he-IL.xml index 07c1667f44639..2157e05528c08 100644 --- a/translation/dest/site/he-IL.xml +++ b/translation/dest/site/he-IL.xml @@ -72,10 +72,10 @@ העדפת וריאנט קביעה כוריאנט הראשי מחיקה מכאן והלאה - הסתרת מהלכים חלופיים - הצגת מהלכים חלופיים + הסתרת מהלכים חלופיים + הצגת מהלכים חלופיים וריאנט יחיד - העתקת ה-PGN של הוריאנט + העתקת ה־PGN של הוריאנט מסע הפסד וריאנט ניצחון וריאנט @@ -92,7 +92,7 @@ דירוג ממוצע: %s משחקים אחרונים המשחקים המובילים - משחקים על־גבי לוח של שחקנים עם דירוג פיד״ה של %1$s+ מ-%2$s עד %3$s + משחקים על־גבי לוח של שחקנים עם דירוג פיד״ה של %1$s+ מ־%2$s עד %3$s מט בעוד חצי מהלך %s מט בעוד %s חצאי מהלכים @@ -233,7 +233,7 @@ שם משתמש שם משתמש או דוא״ל שינוי שם המשתמש - באותיות לועזיות ניתן להחליף אותיות קטנות בגדולות, למשל מ-\"johndoe\" ל-\"JohnDoe\". + באותיות לועזיות ניתן להחליף אותיות קטנות בגדולות, למשל מ־\"johndoe\" ל־\"JohnDoe\". שינוי שם המשתמש. ניתן לעשותו פעם אחת בלבד, ורק על ידי החלפת אותיות גדולות בקטנות ולהיפך. ודאו ששם המשתמש שלכם מתאים גם לילדים. לא תוכלו לשנות אותו מאוחר יותר וחשבונות עם שמות משתמש לא הולמים יסגרו! רק לצורך איפוס הסיסמה. @@ -245,10 +245,10 @@ שכחת סיסמה? הסיסמה הזו נפוצה ביותר וקלה מדי לניחוש. נא לא להשתמש בשם המשתמש בתור הסיסמה. - השתמשת בסיסמה שלך באתר אחר, ויתכן שהיא מועדת לפריצה. כדי להגן על חשבונך בליצ׳ס, עליך להגדיר סיסמה חדשה. תודה על ההבנה. + השתמשת בסיסמה שלך באתר אחר, ויתכן שהיא מועדת לפריצה. כדי להגן על חשבונך ב־Lichess, עליך להגדיר סיסמה חדשה. תודה על ההבנה. את/ה עוזב/ת את Lichess - לעולם אל תקלידו את סיסמתכם בליצ׳ס באף אתר אחר! - מעבר ל-%s + לעולם אל תקלידו את סיסמתכם ב־Lichessבאף אתר אחר! + מעבר ל־%s אל תשתמשו בסיסמה שהציע לכם אדם אחר. הוא ישתמש בה כדי לגנוב את חשבונכם! אל תשמשו בכתובת מייל שהציע אדם אחר. הוא ישתמש בה כדי לגנוב את חשבונכם. עזרה עם אימייל האישור @@ -473,10 +473,10 @@ זה CAPTCHA של שחמט. לחץ/י על הלוח כדי לעשות מהלך, כדי להוכיח שאת/ה בן אנוש. - אנא בצע/י את המהלך הנכון בלוח ה-Captcha. + אנא בצע/י את המהלך הנכון בלוח ה־Captcha. לא מט - מט ב-1 ללבן - מט ב-1 לשחור + מט ב־1 ללבן + מט ב־1 לשחור נסו שוב מתחבר מחדש לא מחובר @@ -517,7 +517,7 @@ גרף פחות מדקה %s - פחות מ- %s דקות + פחות מ־ %s דקות פחות מ%s דקות פחות מ%s דקות @@ -533,9 +533,6 @@ בחר/י שם בטוח מאוד לטורניר. כל דבר לא הולם ולו במעט עלול לגרום לסגירת חשבונך. השאירו ריק כדי לקרוא לטורניר על שם שחקן/ית שח דגול/ה. - אנו ממליצים לא לגעת בזה. - אם תגדיר/י תנאי כניסה, יהיו בטורניר פחות שחקנים. - הצגת הגדרות מתקדמות הפכו את הטורניר לפרטי, והגבילו את הכניסה עם סיסמה הצטרף/י צא/י @@ -575,8 +572,7 @@ אם אין, השאירו ריק פרופיל עריכת פרופיל - שם פרטי - שם משפחה + שם אמיתי הגדירו את הסמליל שלכם סמליל ישנה הגדרה שמאפשרת להסתיר את כל הסמלילים באתר. @@ -625,9 +621,7 @@ סיבה מה הבעיה? רמאות - העלבה הטרלה - מניפולציה בדירוג אחר הדביקו את הקישור למשחק(ים) והסבירו מה לא בסדר בהתנהגות המשתמש. אל תכתבו סתם ״השחקן/ית מרמה״, הסבירו לנו כיצד הגעתם למסקנה הזו. הדיווח יטופל מהר יותר אם ייכתב באנגלית. בבקשה לספק לפחות קישור אחד למשחק עם רמאות. @@ -662,10 +656,11 @@ איטי בתוך הלוח מחוץ ללוח + כל משבצות הלוח במשחקים איטיים תמיד אף פעם - %1$s מתחרה ב-%2$s + %1$s מתחרה ב־%2$s ניצחון הפסד %1$s נגד %2$s ב%3$s @@ -749,7 +744,7 @@ המשחק הסימולטני אינו קיים. חזרה לדף הבית של המשחקים הסימולטניים משחק סימולטני מערב שחקן יחיד אשר משחק נגד שחקנים רבים בו זמנית. - מתוך 50 משחקים בו־זמנית, פישר ניצח ב-47 משחקים, השיג 2 תוצאות תיקו והפסיד במשחק אחד. + מתוך 50 משחקים בו־זמנית, פישר ניצח ב־47 משחקים, השיג 2 תוצאות תיקו והפסיד במשחק אחד. המושג נלקח מאירועים בעולם האמיתי. בחיים האמיתיים, המארח/ת עובר/ת משולחן לשולחן ומשחק/ת מהלך אחד בכל פעם. כאשר המשחק הסימולטני מתחיל, כל שחקן מתחיל את המשחק עם המארח, אשר משחק בתור הלבן. המשחק הסימולטני מסתיים כאשר כל המשחקים מסתיימים. המשחקים הסימולטניים הם תמיד לא מדורגים. האפשרויות של: \"נסה שוב\", ״החזר מהלך״ ו\"הוסף זמן\" מבוטלות. @@ -800,9 +795,9 @@ %1$s שחקני %2$s השבוע. דירוגך במשחקי %1$s הוא %2$s. - דירוגך גבוה יותר מ-%1$s משחקני %2$s. - %1$s טוב יותר מ-%2$s משחקני ה%3$s. - יותר טוב מ-%1$s משחקני ה־%2$s + דירוגך גבוה יותר מ־%1$s משחקני %2$s. + %1$s טוב יותר מ־%2$s משחקני ה%3$s. + יותר טוב מ־%1$s משחקני ה־%2$s טרם נקבע לך דירוג במשחקי %s. הדירוג שלך מצטבר @@ -858,7 +853,7 @@ ניתוח המשחק %1$s מארח/ת %2$s - %1$s מצטרף/ת ל-%2$s + %1$s מצטרף/ת ל־%2$s %1$s אוהב/ת את %2$s הצטרפות מהירה לובי @@ -954,7 +949,7 @@ ושמרו %s המשכים מוגדרים מראש ושמרו %s המשכים מוגדרים מראש - קיבלתם הודעה פרטית מ-Lichess. + קיבלתם הודעה פרטית מ־Lichess. לחצו כאן כדי לקרוא אותה מצטערים :( נאלצנו להשעות אותך לזמן מה. @@ -981,7 +976,7 @@ Blitz Rapid Classical - משחקים מהירים בטירוף: פחות מ-30 שניות על השעון + משחקים מהירים בטירוף: פחות מ־30 שניות על השעון משחקים מהירים מאוד: פחות מ3 דקות על השעון משחקים מהירים: בין 3 ל8 דקות משחקים זריזים: בין 8 ל25 דקות @@ -1003,9 +998,9 @@ אתה עדיין לא יכול לפרסם בפורום זה. שחק כמה משחקים! עקוב בטל את המעקב - תייג/ה אותך ב- %1$s. - %1$s הזכיר/ה אותך בהודעה ב-\"%2$s\". - הזמין אותך ל-\"%1$s\". + תייג/ה אותך ב־ %1$s. + %1$s הזכיר/ה אותך בהודעה ב־\"%2$s\". + הזמין אותך ל־\"%1$s\". %1$s הזמין/ה אותך ללוח הלמידה \"%2$s\". את/ה כעת חבר/ה בקבוצה. הצטרפת אל \"%1$s\". @@ -1039,7 +1034,7 @@ צבע מנחה המשחק זמן התחלה משוער הצג ב%s - גרום למשחק להיות פומבי ב-%s. בטל בשביל משחק סימולטני פרטי. + גרום למשחק להיות פומבי ב־%s. בטל בשביל משחק סימולטני פרטי. תיאור המשחק הסימולטני משהו שאת/ה רוצה להגיד למשתתפים? %s זמין לתחביר מתקדם יותר. @@ -1071,7 +1066,7 @@ לא תוכל/י להתחיל משחק חדש עד גמר הנוכחי. מאז עד - משחקים מדורגים אשר נדגמו מכלל שחקני ליצ׳ס + משחקים מדורגים אשר נדגמו מכלל שחקני Lichess הפוך צד סגירת החשבון תבטל את פנייתך הטיפים שלנו לארגון אירועים diff --git a/translation/dest/site/hi-IN.xml b/translation/dest/site/hi-IN.xml index 38b209c413d2a..84e5eaef88d6c 100644 --- a/translation/dest/site/hi-IN.xml +++ b/translation/dest/site/hi-IN.xml @@ -463,9 +463,6 @@ टूर्नामेंट के लिए उचित नाम चुनिए । थोड़ा सा भी अनुचित नाम चुनने से आपका ख़ाता बंद हो सकता है। खाली रखने पर बेतरतीब ढंग से कोई एक सर्वश्रेष्ठ शतरंज के खिलाड़ी के बाद टूर्नामेंट का नाम चुना जाएगा। - हम इन चिज़ों को छूने की सलाह नहीं देते । - यदि अत्यधिक एवं अनुचित शर्ते रखे तो आपके टूर्नामेंट में कम खिलाड़ियों होंगे। - एडवांस्ड सेटिंग्स दिखाएं प्रतियोगिता को निजी बनाएं, और पासवर्ड के साथ पहुंच को प्रतिबंधित करें हिस्सा लें छोड़ के जाएं @@ -504,8 +501,6 @@ यदि कोई नहीं, खाली छोड़ दो प्रोफ़ाइल प्रोफ़ाइल संपादित करें - प्रथम नाम - अंतिम नाम संपूर्ण साइट पर सभी उपयोगकर्ता विशेषताओं को छिपाने के लिए एक सेटिंग है जीवनी धन्यवाद! @@ -549,9 +544,7 @@ कारण बात क्या है? धोखेबाज़ी - अपमान ट्रोल - जब एक उपयोगकर्ता (lichess. org/report) की रिपोर्टिंग करते हैं, तो \"कारण\" ड्रॉपडाउन के संभावित चयनों में से एक। यह रिपोर्टिंग खिलाड़ियों के लिए है जो उनकी रेटिंग में हेरफेर करते हैं, या तो इसे बढ़ाते हैं या नकली खातों के साथ कृत्रिम रूप से घटाते हैं दूसरा खेल/खेलों के लिंक को लगाएं (paste) और बताएँ की यूज़र के व्यवहार में क्या खराबी है| कृपया ठगे गए खेल के लिए कम से कम एक लिंक प्रदान करें। diff --git a/translation/dest/site/hr-HR.xml b/translation/dest/site/hr-HR.xml index fc677db2d662d..bed122e319954 100644 --- a/translation/dest/site/hr-HR.xml +++ b/translation/dest/site/hr-HR.xml @@ -485,9 +485,6 @@ računalnu analizu, chat partije i URL za dijeljenje. Odaberi vrlo siguran naziv za turnir. Sve imalo neprikladno može trajno zatvoriti tvoj profil. Ako ostaviš prazno, turnir će se nazvati po nasumičnom velemajstoru. - Preporučamo da ne ovo ne dira. - Ako se postavi uvjet za sudjelovanje, turnir će imati manje igrača. - Prikaži napredne postavke Učini turnir privatnim i ograniči pristup lozinkom Pridruži se Odustani @@ -526,8 +523,6 @@ računalnu analizu, chat partije i URL za dijeljenje. Nemaš rejting? Ostavi polje prazno Profil Uredi profil - Ime - Prezime Životopis Hvala! Linkovi društvenih mreža @@ -570,9 +565,7 @@ računalnu analizu, chat partije i URL za dijeljenje. Razlog U čemu je problem? Varanje - Vrijeđanje Provokacija - Manipulacija rejtingom Ostalo Zalijepi link na partiju/e u pitanju i objasni što nije u redu s ponašanjem korisnika. Nemoj samo reći \"varao je\", nego reci kako si došao/la do tog zaključka. Tvoja prijava bit će obrađena brže ako ju napišeš na engleskom jeziku. Molimo navedite barem jedan link igre u kojoj je igrač varao. diff --git a/translation/dest/site/hu-HU.xml b/translation/dest/site/hu-HU.xml index 6a01ed9b523bc..ce1953df0071a 100644 --- a/translation/dest/site/hu-HU.xml +++ b/translation/dest/site/hu-HU.xml @@ -465,9 +465,6 @@ Válassz egy nevet a versenyednek. Akár a legkisebb pontatlanság is a fiókod zárolását eredményezheti. Hagyd üresen, ha azt szeretnéd, hogy a versenyed egy nagymesterről kapjon nevet. - Azt javasoljuk, hogy ne változtass ezeken. - Követelmények beállítása esetén kevesebb játékos lesz a versenyen. - Haladó beállítások megjelenítése A torna priváttá tétele a fenti jelszóval Csatlakozás Visszavon @@ -507,8 +504,6 @@ Ha nincs, hagyd üresen Profil Profil szerkesztése - Keresztnév - Vezetéknév Állítsd be az ikonod Ikon Van egy beállítás amivel elrejtheted az összes felhasználói ikont az egész oldalon. @@ -555,9 +550,7 @@ Ok Probléma meghatározása Csalás - Sértegetés Trollkodás - Pontszám manipuláció Egyéb Másold be a játék(ok) linkjét, és mondd el, mi a gond a játékos viselkedésével. Ne csak annyit írj, hogy \"csalt\", hanem próbáld elmondani, miből gondolod ezt. A jelentésedet hamarabb feldolgozzák, ha angolul írod. Kérünk, legalább adj meg linket legalább egy csalt játszmához. diff --git a/translation/dest/site/hy-AM.xml b/translation/dest/site/hy-AM.xml index c5dc6ab3b114a..92fd850aac1b5 100644 --- a/translation/dest/site/hy-AM.xml +++ b/translation/dest/site/hy-AM.xml @@ -461,9 +461,6 @@ Ընտրեք շատ ապահով անվանում մրցաշարի համար։ Եթե անվանումը թվա նույնիսկ փոքր-ինչ անհարիր, Ձեզ կարող են արգելափակել։ Թողեք դատարկ` մրցաշարը պատահական գրոսմայստերի պատվին կոչելու համար։ - Խորհուրդ չենք տալիս սահմանել այդ պայմանները։ - Եթե Դուք սահմանեք մասնակցության պայմանները, Ձեր մրցաշարին կարող են մասնակցել ավելի քիչ թվով խաղացողներ։ - Ցույց տալ ընդլայնված կարգավորումները Մրցաշարը դարձնել փակ և մուտքը սահմանափակել գաղտնաբառով Միանալ Չեղարկել @@ -502,8 +499,6 @@ Կամ թողնել դատարկ Պրոֆիլ Խմբագրել անձնագիրը - Անուն - Ազգանուն Կենսագրություն Շնորհակալությո՛ւն։ Հղումներ սոցցանցերին @@ -545,9 +540,7 @@ Պատճառ Ի՞նչ է պատահել խաբեբա - վիրավորանք Թրոլինգ - Կեղծարարություններ վարկանիշի հետ այլ Կիսվեք մեզ հետ Այն խաղերի հղումներով, որտեղ կարծում եք, որ կանոնները խախտվել են և նկարագրեք, թե ինչն է սխալ: Բավական չէ պարզապես գրել \"Նա խարդախում է\", խնդրում ենք նկարագրել, թե ինչպես եք եկել այս եզրակացության: Մենք ավելի արագ կաշխատենք, եթե գրեք անգլերեն: Խնդրում ենք ավելացնել առնվազն մեկ խաղի հղում, որտեղ ձեր կարծիքով խախտվել են կանոնները: diff --git a/translation/dest/site/ia-IA.xml b/translation/dest/site/ia-IA.xml index 26615d09d3525..89642c35c78f7 100644 --- a/translation/dest/site/ia-IA.xml +++ b/translation/dest/site/ia-IA.xml @@ -380,9 +380,6 @@ Selige un nomine assatis secur pro le torneo. Mesmo alco levemente inappropriate pote causar que tu conto sia terminate. Lassa iste campo vacue pro que le torneo ha un nomine de un Grande Maestro al hasardo. - Nos recommenda non modificar isto. - Si tu impone conditiones de entrata, tu torneo habera minus jocatores. - Monstrar le configurationes avantiate Facer le torneo esser private, e restringer le accesso con un contrasigno Participar Retirar se @@ -417,8 +414,6 @@ Si necun, lassa lo vacue Profilo Modificar profilo - Prenomine - Nomine de familia Biographia Gratias! Ligamines de medios social @@ -457,9 +452,7 @@ Ration Que es le problema? Fraude - Insulto Troll - Manipulation de classification Altere Colla le ligamine al joco(s) e explica lo que es improprie re le conducto del usator. Non dice solmente \"ille frauda\", ma informa como tu arrivava a iste conclusion. Tu reporto essera processate plus rapidemente si tu scribe in anglese. Per favor, provide al minus un ligamine de un partita fraudate. diff --git a/translation/dest/site/id-ID.xml b/translation/dest/site/id-ID.xml index b8d7b158a2bb8..e06add628a579 100644 --- a/translation/dest/site/id-ID.xml +++ b/translation/dest/site/id-ID.xml @@ -428,9 +428,6 @@ Pilih nama yang paling aman untuk turnamen. Nama apapun yang bahkan sedikit tidak pantas akan mengakibatkan akun Anda ditutup. Biarkan kosong untuk nama turnamen setelah pengacakan Grandmaster. - Kita rekomendasikan jangan sentuh ini. - Jika Anda atur persyaratan masuk, turnamen yang Anda buat akan memiliki lebih sedikit pemain. - Tampilkan pengaturan lanjutan Jadikan turnamen pribadi, dan batasi akses dengan kata sandi Gabung Keluar @@ -470,8 +467,6 @@ Jika tidak ada, biarkan kosong Profil Ubah profil - Nama depan - Nama Belakang Sunting flair anda Flair Terdapat setting untuk menyembunyikan semua flair pengguna pada seluruh situs. @@ -516,9 +511,7 @@ Alasan Apa masalahnya? Cheat - Penghinaan Jebakan - Manipulasi rating Lainnya Paste link berikut ke dalam permainan dan jelaskan apa masalah tentang pengguna ini. Harap berikan setidaknya satu tautan ke permainan yang curang. diff --git a/translation/dest/site/is-IS.xml b/translation/dest/site/is-IS.xml index c9c1913404157..054038bb8dfb1 100644 --- a/translation/dest/site/is-IS.xml +++ b/translation/dest/site/is-IS.xml @@ -416,9 +416,6 @@ Veldu mjög öruggt nafn fyrir mótið. Eitthvað sem er hið minnsta óviðeigandi getur leitt til þess að reikningi verði lokað. Skildu eftir autt til að nefna mótið eftir handahófskennt völdum stórmeistara. - Við mælum með því að breyta þessu ekki. - Ef þú setur skilyrði við þátttöku mun mótið hafa færri leikmenn. - Sýna ítarlegar stillingar Loka móti, og takmarka aðgang með aðgangsorði Taka þátt afturkalla @@ -457,8 +454,6 @@ Autt ef á ekki við Prófíll Stillingar - Fornafn - Eftirnafn Lífssaga Takk fyrir! Samfélagsmiðlatengill @@ -495,7 +490,6 @@ Ástæða Hvað er að? Svindl - Móðgun Tröll Annað Límdu hér hlekk(i) að leik eða leikjum og útskýrðu hvað er að hegðun notandans. Ekki segja bara ,,hann svindlar\'\', en segðu okkur hvernig þú komst að þeirri niðurstöðu. Unnið verður örar úr kvörtuninni ef hún er skrifuð á ensku. diff --git a/translation/dest/site/it-IT.xml b/translation/dest/site/it-IT.xml index 29cb8e9c5e8c4..ac0b8633e456a 100644 --- a/translation/dest/site/it-IT.xml +++ b/translation/dest/site/it-IT.xml @@ -466,9 +466,6 @@ analizzarla con il computer, commentarla in chat, e condividerla tramite un indi Scegli un nome appropriato per il torneo. Qualsiasi cosa anche solo lievemente inappropriata potrebbe comportare l\'eliminazione del tuo account. Lascia vuoto il nome del torneo per chiamarlo con il nome di un giocatore famoso casuale. - Si consiglia di non toccare queste impostazioni. - Se imponi condizioni d\'accesso il torneo avrà un minor numero di giocatori. - Mostra impostazioni avanzate Rendi privato il torneo e limita l\'accesso con una password Unisciti Ritirati @@ -508,8 +505,6 @@ analizzarla con il computer, commentarla in chat, e condividerla tramite un indi Se nessuno, lasciare vuoto Profilo Modifica profilo - Nome - Cognome Imposta la tua icona Icona Esiste un\'impostazione per nascondere le icone di tutti gli utenti, sull\'intero sito. @@ -556,9 +551,7 @@ analizzarla con il computer, commentarla in chat, e condividerla tramite un indi Motivo Qual è il problema? Imbrogli - Insulti Provocazioni - Manipolazione del rating Altro Incolla il link della partita/e e spiega cosa non va con questo giocatore. Non dire soltanto \"ha imbrogliato\", ma specifica come sei arrivato a questa conclusione. Il tuo report verrà processato più velocemente se scritto in lingua inglese. Si prega di fornire almeno un collegamento link di una partita in cui il giocatore ha imbrogliato. diff --git a/translation/dest/site/ja-JP.xml b/translation/dest/site/ja-JP.xml index 7406145dafbf2..d445bb096f881 100644 --- a/translation/dest/site/ja-JP.xml +++ b/translation/dest/site/ja-JP.xml @@ -69,8 +69,8 @@ 変化を主手順にする 主手順にする これ以降を削除 - 変化手順をかくす - 変化手順を表示する + 変化手順をかくす + 変化手順を表示する 変化として表示 手順の PGN をコピー @@ -435,9 +435,6 @@ トーナメントの名前は無難なものにしてください。 少しでも不適切な名前の場合、あなたのアカウントを停止することがあります。 空白のままにしておくと、自動でランダムな有名選手の名前がつきます。 - ここは触らないことを推奨します。 - 参加条件を決めると、当然参加者は少なくなります。 - 「高度な設定」を表示 トーナメントを「非公開」にすると参加にパスワードが必要になります 参加する 参加をやめる @@ -477,8 +474,7 @@ ない場合は空白で プロフィール プロフィールの編集 - - + 実名 フレアを設定 フレア サイト全体でフレアを非表示にする設定があります。 @@ -524,9 +520,7 @@ 理由 どうされましたか? ソフト使用 - 侮辱 荒らし - レーティング不正操作 その他 問題のゲームへのリンクを貼って、相手ユーザーの問題点を説明してください。ただ「イカサマだ」と言うのではなく、なぜそう思うか理由を書いてください。英語で書くと対応が早くできます。 不正のあった対局 1 局以上へのリンクを添えてください。 @@ -561,6 +555,7 @@ 遅い 盤の内 盤の外 + すべてのマスに 長時間の対局のみ 常に有効 無効 diff --git a/translation/dest/site/jbo-EN.xml b/translation/dest/site/jbo-EN.xml index 6f46ea39caa42..9b8c69f2bb3df 100644 --- a/translation/dest/site/jbo-EN.xml +++ b/translation/dest/site/jbo-EN.xml @@ -353,8 +353,6 @@ .e\'o ko gasnu lonu na da\'asnu lo cmene be ti poi grinunjvi .e\'unai lonu lo do jaspu ku cu\'urga\'o cu cumki lonu ba\'ucu\'i toltce da\'asnu lo cmene pe lo caxmati mispre cu tecycuxskicauzmi - .e\'unai - arco lo certu gaftercu\'a cmibi\'o ti\'ekla lo kelnemka\'u @@ -386,8 +384,6 @@ lonu noda co\'e cu krinu lonu curmi lonu kunti lo predatni stika lo predatni be do - lo du\'acme - lo lazme\'e velski do fo do ki\'e .urli lo kibro jikca predatni pesai do @@ -422,7 +418,6 @@ mukti ma se mabla lo ka toltinbe - lo ka malsku lo ka trolo lo drata ko jarco lo judri pe lo selkei gi\'e ciksi lo du\'u ma kau se mabla lo seltra .i ko na skuxusra ke po\'o lo du\'u jvatoltinbe kei gi\'e cusku lo nibli be ri be\'o pe do .i lo notci pe do cu jai zmadu se sutra fai lo ka se tcidu kei fau lo nu glibau diff --git a/translation/dest/site/ka-GE.xml b/translation/dest/site/ka-GE.xml index 0171e587e82a3..5c765372adb94 100644 --- a/translation/dest/site/ka-GE.xml +++ b/translation/dest/site/ka-GE.xml @@ -460,9 +460,6 @@ ტურნირისთვის შეარჩიეთ უსაფრთხო სახელი. ნებისმიერი, თუნდაც მცირედი, უტაქტობა გამოიწვევს თქვენი ანგარიშის დახურვას. დატოვეთ ცარიელი, თუ გნებავთ რომ ტურნირს დაერქვას შემთხვევითად შერჩეული ჭადრაკის დიდოსტატის სახელი. - გირჩევთ ამას არ შეეხოთ. - თუ თქვენ შესვლის წინაპირობებს დააყენებთ, თქვენს ტურნირს უფრო ცოტა მოთამაშე ეყოლება. - დამატებითი პარამეტრების ნახვა გახადე ტურნირი დახურული და შეუზღუდე ჭვდომა პაროლით შეუერთდით გავარდნა @@ -501,8 +498,6 @@ არარსებობის შემთხვევაში დატოვე ცარიელი პროფილი პროფილის განახლება - სახელი - გვარი ბიოგრაფია გმადლობთ შეფასებისთვის! სოციალური მედიის ბმულები @@ -544,9 +539,7 @@ მიზეზი მიზეზი ტყული - შეურაცხყოფა პროვოკაცია - რეიტინგის მანიპულაცია სხვა ჩასვით თამაშ(ებ)ის ბმული და მიუთითეთ თუ რა არის საეჭვო მომხმარებლის ქცევაში. გთხოვთ მიუთითო სულ მცირე ერთი პარტიის ბმული სადაც თვლით რომ მოწინააღმდეგემ წესების დარღვევით ითამაშა. diff --git a/translation/dest/site/kaa-UZ.xml b/translation/dest/site/kaa-UZ.xml index 0ca9f3330d144..b05a5e233d69d 100644 --- a/translation/dest/site/kaa-UZ.xml +++ b/translation/dest/site/kaa-UZ.xml @@ -259,7 +259,6 @@ Turnir kalendarı Qatnasıw shártleri: Qosımsha sazlawlar - Qosımsha sazlawlardı kórsetiw Qosılıw Oyınnan shıǵıw Jeńis diff --git a/translation/dest/site/kab-DZ.xml b/translation/dest/site/kab-DZ.xml index 2bca50df0da6d..004dd31c6d8b9 100644 --- a/translation/dest/site/kab-DZ.xml +++ b/translation/dest/site/kab-DZ.xml @@ -244,8 +244,6 @@ Ɛeyyen %s ɣer anebdal Alegdis Zreg alegdis - Issem - Isem n twacult Tameddurt Tanemmirt! Yakan seg Lichess TV diff --git a/translation/dest/site/kk-KZ.xml b/translation/dest/site/kk-KZ.xml index 79e714325daee..5644e60d087b1 100644 --- a/translation/dest/site/kk-KZ.xml +++ b/translation/dest/site/kk-KZ.xml @@ -462,9 +462,6 @@ Жарысты түзу, жөнді сөзбен атаңыз. Кез-келген, тіпті шамалы орынсыз әрекет себебінен тіркелгіңіз жабылуы мүмкін. Елеулі бір шахматшының атымен атау үшін бос қалдырыңыз. - Бұны тиіспеуге кеңес береміз. - Егер кіру талаптарын қойсаңыз, қатысатын ойыншылар саны аз болады. - Қосымша баптауларды көрсету Кіруді құпиясөзбен шектеп, жарысты оңашалаңыз Қосылыңыз Жарыстан шығу @@ -503,8 +500,6 @@ Жоқ болса, бос қалдырыңыз Куәлік Куәлікті өзгерту - Атыңыз - Тегіңіз Өмірбаян Рахмет! Әлеуметтік медиа @@ -546,9 +541,7 @@ Түйіні Не болды? Чит, алдап ойнау - Қорлау Троль, кемсіту - Рейтингпен айла-шарғы Басқа Ойынның (-дардың) сілтемесін қойып, осы ойыншының қай әрекеті орынсыз болғанын түсіндіріп беріңіз. Жай ғана \"ол алдап ойнады\" деп жаза салмай, осы ойға қалай келгеніңізді айтып беріңіз. Сіздің шағымыңыз ағылшын тілінде жазылса, тезірек тексеруден өтеді. Кемі бір ойынның сілтемесін беруіңізді сұраймыз. diff --git a/translation/dest/site/kmr-TR.xml b/translation/dest/site/kmr-TR.xml index f4987c0ac7004..2f2b1e7142944 100644 --- a/translation/dest/site/kmr-TR.xml +++ b/translation/dest/site/kmr-TR.xml @@ -410,9 +410,6 @@ Ji bo pêşbirkê navekê pir ewle bibijêre. Navekê piçekî nelirê dibe ku bibe sedem ji girtina hesabê te re. Cihê navê vala bihêlî wê bi ketoberî naveke hosteyeke kişikê yê hêja lê were danîn. - Em pêşniyar dikin ku destê xwe nedî van. - Ger tu mercên tevlîbûnê saz bikî, wê kêmtir lîzer hebin di pêşbirka te de. - Sazkariyên pêşketî nîşan bide Tûrnûva bik taybet û gihînê jî bi şîfre tengbik Tevlê bibe Vekişe @@ -450,8 +447,6 @@ Ger nîn be, vala bihêle Profîl Profîlê sererast bike - Nav - Paşnav Biyografî Spas! Têkîliyên Medyaya Civakî @@ -489,7 +484,6 @@ Sedem Mijar çi ye? Hîle - Heqaret Troll Yên din Ji bo lîstikê/an lînk pê ve bike û îzah bike ku çi xeletî hene di hal û hereketên vî bikarhênerê de. Tenê nebêje \"ew hîle dike\", bêje me ku tê çawa gihîştiye wê encamê. Ger bi îngîlîzî binivîsînî wê zûtir hel bibe giliya te. diff --git a/translation/dest/site/kn-IN.xml b/translation/dest/site/kn-IN.xml index da83dff64cdf3..05d104b4599cf 100644 --- a/translation/dest/site/kn-IN.xml +++ b/translation/dest/site/kn-IN.xml @@ -461,9 +461,6 @@ ಪಂದ್ಯಾವಳಿಗೆ ಸುರಕ್ಷಿತ ಹಾಗೂ ಅಪಾಯವಿಲ್ಲದ ಹೆಸರನ್ನು ಆಯ್ಕೆ ಮಾಡಿ. ಕೇವಲ ಸಣ್ಣಮಟ್ಟಿನ ಅನುಚಿತ ಅಥವಾ ಅಸಮಂಜಸ ಹೆಸರುಗಳೂ ಕೂಡ ನಿಮ್ಮ ಖಾತೆಯ ನಿಷೇದಕ್ಕೆ ಕಾರಣವಾಗಬಹುದು. ನಿಮ್ಮ ಪಂದ್ಯಾವಳಿಯನ್ನು ಯಾವುದೋ ಒಬ್ಬರು ಪ್ರಸಿದ್ಧ ಚೆಸ್ ಆಟಗಾರರ ಹೆಸರಿನಲ್ಲಿ ನಾಮಕರಣಗೊಳಿಸಬೇಕೆಂದಿದ್ದರೆ, ಹೆಸರಿನ ಭಾಗವನ್ನು ಖಾಲಿ ಬಿಟ್ಟು ಬಿಡಿ. - ಇದನ್ನು ಬದಲಾಯಿಸಬೇಡಿ ಎಂದು ಶಿಫಾರಸ್ಸು ಮಾಡುತ್ತೇವೆ. - ಇನ್ನೂ ಹೆಚ್ಚಿನ ಪ್ರವೇಶಾರ್ಹತೆಗಳನ್ನು ನಿಗದಿಪಡಿಸಿದರೆ, ನಿಮ್ಮ ಪಂದ್ಯಾವಳಿಗೆ ಲಭ್ಯವಾಗುವ ಆಟಗಾರರ ಸಂಖ್ಯೆ ಕಡಿಮೆಯಾಗುವ ಸಾಧ್ಯತೆಯಿದೆ. - ಇನ್ನೂ ಹೆಚ್ಚಿನ ಅಳವಡಿಕೆಗಳನ್ನು ತೋರಿಸಿ ಈ ಪಂದ್ಯಾವಳಿಯನ್ನು ಖಾಸಗಿಯಾಗಿಸಿ, ಹಾಗೂ ಕೀಲಿಪದ ಅಂದರೆ ಪಾಸ್ವರ್ಡ್ ಬಳಸಿ ಪ್ರವೇಶವನ್ನು ನಿರ್ಬಂಧಿಸಿ ಸೇರಿ ಹಿಂಪಡೆಯಿರಿ @@ -502,8 +499,6 @@ ಧಾರಣೆ ಇಲ್ಲವಾದರೆ, ಈ ಜಾಗ ಖಾಲಿ ಬಿಟ್ಟು ಬಿಡಿ ಪ್ರೊಫೈಲು ಪ್ರೊಫೈಲ್ ಪರಿಷ್ಕರಿಸಿ - ಹೆಸರು - ಕುಟುಂಬನಾಮ (surname) ನಿಮ್ಮ ಬಗ್ಗೆ ಧನ್ಯವಾದಗಳು! ಸಾಮಾಜಿಕ ಮಾಧ್ಯಮಗಳ ಕೊಂಡಿ @@ -545,9 +540,7 @@ ಕಾರಣ ವಿಷಯ ಅಥವಾ ಸಮಸ್ಯೆ ಏನು? ಮೋಸದಾಟ - ಅವಮಾನಿಸಿದ್ದಾರೆ ಉಪದ್ರವಿ - ಧಾರಣೆಯಲ್ಲಿ ವಂಚನೆ ಇತರೆ ಮೋಸ ನಡೆದಿದೆ ಎಂಬ ಸಂಶಯವಿರುವ ಆಟದ (ಆಟಗಳ) ಕೊಂಡಿ ಅಥವಾ ಲಿಂಕ್ ಅನ್ನು ಇಲ್ಲಿ ಅಂಟಿಸಿ. ಹಾಗೂ, ಈ ವ್ಯಕ್ತಿಯ ಅಥವಾ ಖಾತೆಯ ವರ್ತನೆಯಲ್ಲಿ ಏನು ಸಮಸ್ಯೆ ಇದೆ ಎಂಬುದನ್ನು ಸ್ಪಷ್ಟವಾಗಿ ವಿವರಿಸಿ. ಕೇವಲ \"ಅವರು ಮೋಸ ಮಾಡಿದ್ದಾರೆ\" ಎಂದಷ್ಟೇ ಬರೆದು ಬಿಡಬೇಡಿ. ಬದಲಾಗಿ, ಹೇಗೆ ಈ ನಿರ್ಧಾರಕ್ಕೆ ಬಂದಿರಿ ಎಂಬುದನ್ನು ವಿವರಿಸಿ. ನಿಮ್ಮ ದೂರು ಇಂಗ್ಲಿಷ್ ಭಾಷೆಯಲ್ಲಿದ್ದರೆ ಬೇಗನೆಯೇ ಪರಿಹಾರ ಸಿಗುವ ಸಾಧ್ಯತೆ ಇದೆ. ದಯವಿಟ್ಟು ಮೋಸಕ್ಕೊಳಗಾದ ಒಂದಾದರೂ ಆಟದ ಕೊಂಡಿಯನ್ನು ಇಲ್ಲಿ ಹಂಚಿಕೊಳ್ಳಿ. diff --git a/translation/dest/site/ko-KR.xml b/translation/dest/site/ko-KR.xml index 55f4f6a8b83ac..d69f7e3828067 100644 --- a/translation/dest/site/ko-KR.xml +++ b/translation/dest/site/ko-KR.xml @@ -432,9 +432,6 @@ 토너먼트 이름은 무난한 것으로 정해주십시오. 조금이라도 부적절한 이름일 경우에는 계정이 정지될 수도 있습니다. 토너먼트 이름을 공란으로 두면, 임의의 그랜드마스터 선수 이름으로 선택됩니다. - 이 설정은 되도록이면 그대로 두는 것을 추천합니다. - 참가조건을 설정하면, 참가자 수가 적어집니다. - 고급 설정 보기 토너먼트를 비공개로 바꾸고, 비밀번호가 있어야만 들어갈 수 있습니다. 참가하기 중단 @@ -474,8 +471,7 @@ 없으면 무시하세요 프로필 프로필 수정 - 이름 - + 본명 소개 국가/지역 감사합니다! @@ -517,9 +513,7 @@ 이유 무엇이 문제인가요? 부정행위 - 모욕 분란 조장 - 레이팅 조작 기타 게임 URL 주소를 붙여넣으시고 해당 사용자가 무엇을 잘못했는지 설명해 주세요. 부정행위가 존재하는 게임의 링크를 적어도 하나는 적어주세요. diff --git a/translation/dest/site/la-LA.xml b/translation/dest/site/la-LA.xml index d610118c762d8..837ca6892f2f8 100644 --- a/translation/dest/site/la-LA.xml +++ b/translation/dest/site/la-LA.xml @@ -396,8 +396,6 @@ Facere certamen novum Tabula certaminum Occasus provectus - Commendamus te non hos mutare. - Ostende occasus provectus Iungere Decedere Puncta @@ -425,8 +423,6 @@ Gradus %s Codex Mutare codicem - Praenomen - Nomen gentilicium Biographia Gratias tibi! Prius in Lichess TV @@ -461,7 +457,6 @@ Ratio Quid est? Fraus - Iniuria Barbarus Alia Glutina inscriptionem lud(i/orum) et explica quid de moribus usuarii malum est. diff --git a/translation/dest/site/lb-LU.xml b/translation/dest/site/lb-LU.xml index cc05128a13d49..6613f8068c510 100644 --- a/translation/dest/site/lb-LU.xml +++ b/translation/dest/site/lb-LU.xml @@ -70,8 +70,8 @@ Variant opwäerten Haaptvariant maachen Vun hei läschen - Varianten zesummeklappen - Varianten opklappen + Varianten zesummeklappen + Varianten opklappen Variant forcéieren PGN-Variant kopéieren Zuch @@ -466,9 +466,6 @@ Wiehl en ganz sécheren Numm fir däin Turnéier. Alles nemmen liicht Onangemessenes kéint zum Zoumaachen vun dengem Konto féieren. Eidel loossen, wann den Turnéier no engem bekannten Schachspiller soll benannt ginn. - Mir recommandéieren déi net unzepaken. - Mat Teilnahmebedingungen wäert däin Turnéier maner Spiller hunn. - Erweidert Optiounen uweisen Turnéier privat maachen an Accès mat Passwuert beschränken Bäitrieden Zeréckzéihen @@ -508,8 +505,7 @@ Eidel loossen, falls keng Profil Profil änneren - Virnumm - Numm + Richtegen Numm Biographie Land oder Regioun Merci! @@ -553,9 +549,7 @@ Grond Wat ass den Problem? Bedruch - Beleidegung Troll - Wäertungsmanipulatioun Aner Post den Link vun Partie(n) and erklär wat den Problem mat dësem Benotzer sengem Verhalen ass. So net just \"Hien fuddelt\", mee so eis wéi du zu dëser Konklusioun komm bass. Däin Rapport gëtt méi schnell veraarbecht wann en op Englesch ass. Wannechgelift gëff eis op mannst een Link zu enger Partie mat Bedruch. @@ -590,6 +584,7 @@ Lues Um Briet Außerhalb vum Briet + All d\'Felder um Briet An luesen Partien Ëmmer Ni diff --git a/translation/dest/site/lt-LT.xml b/translation/dest/site/lt-LT.xml index 66a91244c200c..37330429494c3 100644 --- a/translation/dest/site/lt-LT.xml +++ b/translation/dest/site/lt-LT.xml @@ -532,9 +532,6 @@ kompiuterinę analizę, partijos pokalbį bei URL dalinimuisi. Pasirinkite labai saugų turnyro pavadinimą. Kas nors bent kiek netinkamo gali lemti jūsų paskyros uždarymą. Palikus tuščią, turnyras bus pavadintas pagal atsitiktinį didmeistrį. - Rekomenduojame šitų neliesti. - Jeigu pridėsite dalyvavimo sąlygų, jūsų turnyre bus mažiau žaidėjų. - Rodyti papildomas nuostatas Padaryti turnyrą privačiu, ir apriboti patekimą su slaptažodžiu Prisijungti Pasitraukti @@ -574,8 +571,6 @@ kompiuterinę analizę, partijos pokalbį bei URL dalinimuisi. Jei neturite, palikite tuščią Profilis Redaguoti profilį - Vardas - Pavardė Pasirinkite savo atskiriamąjį ženklą - avatarą Atskiriamasis ženklas Yra nustatymas leidžiantis atjungti visų žaidėjų skiriamuosius ženklus. @@ -624,9 +619,7 @@ kompiuterinę analizę, partijos pokalbį bei URL dalinimuisi. Priežastis Kas nutiko? Sukčiaviavo - Ižeidė „Troll\'ino“ - Reitingo manipuliacija Kita Įdėkite nuorodą į partiją(-as) ir paaiškinkite, kas netinkamo yra šio vartotojo elgsenoje. Paminėkite, kaip priėjote prie tokios išvados. Jūsų pranešimas bus apdorotas greičiau, jei bus pateiktas anglų kalba. Pateikite bent vieną nuorodą į partiją, kurioje buvo sukčiauta. diff --git a/translation/dest/site/lv-LV.xml b/translation/dest/site/lv-LV.xml index ef03892cc23e0..bb9a741bdd809 100644 --- a/translation/dest/site/lv-LV.xml +++ b/translation/dest/site/lv-LV.xml @@ -492,9 +492,6 @@ Izvēlieties drošu turnīra nosaukumu. Jebkas kaut nedaudz nepiedienīgs var novest pie jūsu konta slēgšanas. Atstājiet neaizpildītu, lai nosauktu turnīru kāda ievērojama šahista vārdā. - Iesakām šeit neko nemainīt. - Ja uzstādīsiet dalības noteikumus, jūsu turnīrā piedalīsies mazāk spēlētāju. - Rādīt lietpratēju uzstādījumus Padarīt turnīru privātu, un ierobežot piekļuvi ar paroli Pievienoties Izstāties @@ -533,8 +530,6 @@ Ja nav piešķirts, atstājiet tukšu Profils Labot profilu - Vārds - Uzvārds Biogrāfija Paldies! Sociālo mediju saites @@ -577,9 +572,7 @@ Iemesls Kas par lietu? Krāpšanās - Apvainošana Troļļošana - Reitinga manipulācija Cits Ielīmējiet spēles saiti un paskaidrojiet, kas nav kārtībā ar lietotāja uzvedību. Nepietiks, ja tikai norādīsiet, ka \"lietotājs krāpjas\" — lūdzu, pastāstiet, kā nonācāt pie šī secinājuma. Ja jūsu ziņojums būs rakstīts angliski, par to varēsim parūpēties ātrāk. Lūdzu, norādiet vismaz vienu saiti uz spēli, kurā pretinieks ir krāpies. diff --git a/translation/dest/site/mg-MG.xml b/translation/dest/site/mg-MG.xml index 0a7f0811bb291..4b6bc624a65a8 100644 --- a/translation/dest/site/mg-MG.xml +++ b/translation/dest/site/mg-MG.xml @@ -333,8 +333,6 @@ Toroy any amin\'ny tompon-andraikitra i %s Mombamomba Ovay ny mombamomba anao - Anarana - Fanampin\'anarana Biographie Misaotra! Teto amin\'ny Lichess TV @@ -363,9 +361,7 @@ Antony Inona ny olana? Fanambakana - Fanopana Fananihanina - Fanovana laharana Antony hafa Mikatona ity lohahevitra ity. Bilaogy diff --git a/translation/dest/site/mk-MK.xml b/translation/dest/site/mk-MK.xml index ad680fe71e5fc..750f0f2a7049c 100644 --- a/translation/dest/site/mk-MK.xml +++ b/translation/dest/site/mk-MK.xml @@ -456,9 +456,6 @@ Одбери многу безбедно име за турнирот. И најмалку непристојни работи можат да ви ја затворат сметката. Остави празно за турнирот да биде крстен по шаховски првак. - Препорачуваме да не ги менувате овие поставки. - Ако поставите услови за влез, вашиот турнир ќе има помалку играчи. - Покажи ги напредните поставки Направи го турнирот приватен и ограничи го пристапот со лозинка Приклучи се Предади @@ -497,8 +494,6 @@ Без рејтинг? Остави го полето празно Профил Уреди го профилот - Име - Презиме Биографија Благодарам! Врски на друштвени мрежи @@ -540,9 +535,7 @@ Причина Каде е проблемот? Мамење - Навреда Трол - Манипулација на рејтинг Друго Внесете линк од играта/игрите и објаснете каде е проблемот во однесувањето на овој корисник. Немојте само да обвините за мамење, туку објаснете како дојдовте до тој заклучок. Вашата пријава ќе биди разгледана побрзо ако е напишана на англиски јазик. Ве молиме доставете барем една врска до партија со мамење. diff --git a/translation/dest/site/ml-IN.xml b/translation/dest/site/ml-IN.xml index a0c1e46153f1e..6a2a6e65950a2 100644 --- a/translation/dest/site/ml-IN.xml +++ b/translation/dest/site/ml-IN.xml @@ -407,9 +407,6 @@ ടൂർണമെന്റിന് വളരെ സുരക്ഷിതമായ പേര് കൊടുക്കുക. എന്തെങ്കിലും ചെറിയ ഔചിത്യപൂർണമല്ലാത്ത പ്രവൃത്തി മതി നിങ്ങളുടെ അക്കൗണ്ട് പൂട്ടാൻ. ടൂർണമെന്റിന്റെ പേര് ഒഴിവാക്കി വിട്ടാൽ ഏതെങ്കിലും ഗ്രാൻഡ്മാസ്റ്ററുടെ പേരിലായിരിക്കും ടൂർണമെന്റ് നടത്തപ്പെടുക. - ഇവ തൊടരുതെന്ന് ഞങ്ങൾ ശുപാർശ ചെയ്യുന്നു. - പ്രവേശനത്തിനുള്ള നിബന്ധനങ്ങൾ നിങ്ങൾ സെറ്റ് ചെയ്താൽ, അത് കുറച്ചു കളിക്കാർ മാത്രം നിങ്ങളുടെ ടൂർണമെന്റിൽ കളിക്കുന്നതിനു കാരണമാകും. - നൂതനമായ ക്രമീകരണം ടൂർണമെന്റ് പ്രൈവറ്റ് ആക്കുക പാസ്സ്‌വേർഡ്‌ വെച്ച് സംരക്ഷിക്കുക പങ്കെടുക്കാം റദ്ദാക്കുക @@ -446,8 +443,6 @@ ഒന്നുമില്ലെങ്കിൽ ഒഴിവാക്കിയിടുക രൂപരേഖ രൂപരേഖ തിരുത്താം - നൽകിയ പേര് - അവസാന പേര് ജീവചരിത്രം നന്ദി! സമൂഹമാധ്യമ ലിങ്കുകൾ @@ -486,9 +481,7 @@ കാരണം എന്താണു സംഗതി? ചതി - അധിക്ഷേപം ചൂണ്ട - റേറ്റിംഗ് കൃത്യമം മറ്റുള്ളവ ഗെയിമിന്റെ ലിങ്ക് ഇവിടെ പേസ്റ്റ് ചെയ്യുക. ശേഷം യുസറിന്റെ പെരുമാറ്റത്തിലെ അപാകത ഞങ്ങളോട് വിശദമായി പറയുക. \"they cheat\" എന്ന് പറയരുത്. പക്ഷെ എന്തുകൊണ്ട് നിങ്ങൾ ആ അഭിപ്രായത്തിലേക്ക് എത്തി എന്നാണ് പറയേണ്ടത്. ഇംഗ്ലീഷിൽ എഴുതിയാൽ നിങ്ങളുടെ റിപ്പോർട്ട് വേഗത്തിൽ വിശകലനം ചെയ്യപ്പെടും. കുറഞ്ഞത് ഒരു കളിയുടെയെങ്കിലും ലിങ്ക് നല്‍കേണ്ടതാണ്. diff --git a/translation/dest/site/mn-MN.xml b/translation/dest/site/mn-MN.xml index 10ef6fafc554a..5c9769b6e3a5f 100644 --- a/translation/dest/site/mn-MN.xml +++ b/translation/dest/site/mn-MN.xml @@ -427,9 +427,6 @@ Тэмцээний хувьд маш аюулгүй нэр сонгоно уу. Бүр бага зэрэг зохисгүй зүйл таны данс хаагдах боломжтой болно. Тэмцээний Grandmaster-ийн дараа хоосон орхи. - Бид эдгээрийг хөндөхгүй байхыг зөвлөж байна. - Хэрэв та орох нөхцөлийг тохируулсан бол таны тэмцээн цөөхөн тоглогчтой болно. - Нарийвчилсан тохиргоог харуулах Хувийн тур нууц үгтэйгээр үүсгэх Нэгдэх Тэмцээнийг орхих @@ -469,8 +466,6 @@ Байхгүй бол хоосон орхи Гишүүний хуудас Танилцуулга хуудсаа засварлах - Өөрийн нэр - Эцгийн нэр Нэрнийхээ хажууг чимэглэх Эможи Сайтанд байгаа бүх нэрний хажуу зүйлсийг нуух тохиргоо байдгийг санаарай. @@ -515,9 +510,7 @@ Шалтгаан Шалтгаан? Булхайцсан - Доромжилсон Тохуурхсан - Үнэлгээгээ гуйвуулах Өөр шалтаан Та энэ гишүүнтэй тоглосон өргийнхөө холбоос хаягыг энд хуулж тавиад зүй бус үйлдлийнх нь талаар тайлбар бичнэ үү. Та хэрхэн энэ гишүүнийг булхайцсан үйлдэл хийсэн гэсэн дүгнэлтэд хүрсэн бэ? Хэрэв та гомдлоо англи хэлээр мэдүүлвэл бид илүү түргэн шуурхай арга хэмжээ авах болно. Тоглоомын дор хаяж нэг холбоосыг оруулна уу. diff --git a/translation/dest/site/mr-IN.xml b/translation/dest/site/mr-IN.xml index e87b10cb1b4e7..2a9b717a8a398 100644 --- a/translation/dest/site/mr-IN.xml +++ b/translation/dest/site/mr-IN.xml @@ -428,9 +428,6 @@ क्रीडासत्रासाठी खूप सुरक्षित नाव निवडा. थोडे अनुचित काहीही आपले खाते बंद करू शकते. क्रीडासत्राला उल्लेखनीय बुद्धिबळपटूचे नाव देणीसाठी रिकामे सोडा. - आमच्या मते तुम्ही याला बदलू नये. - आपण प्रविष्टी आवश्यकता सेट केल्यास, आपल्या क्रीडासत्रात कमी खेळाडू असतील. - प्रगत सेटिंग्ज दाखवा क्रीडासत्र खाजगी करा, व पासवर्डने प्रवेश मर्यादित करा सामील व्हा मागे घ्या @@ -469,8 +466,6 @@ काहीही नसल्यास रिक्त सोडा प्रोफाइल प्रोफाईल संपादित करा - नाव - आडनाव चरित्र आभारी आहे सोशल मीडिया दुवे @@ -511,9 +506,7 @@ कारण काय समस्या आहे फसवणूक - अपमान थट्टा - रेटिंग मॅनिपुलेशन इतर खेळाचा दुवा पेस्ट करा आणि या वापरकर्त्याच्या वर्तनाबद्दल काय चुकीचे आहे ते सांगा. फक्त \"ते फसवतात\" असे म्हणू नका, परंतु आपण या निष्कर्षावर कसे आलात ते सांगा. इंग्रजीमध्ये लिहिले असल्यास आपल्या अहवालावर वेगवान प्रक्रिया केली जाईल. कृपया फसवणूक झालेल्या खेळासाठी किमान एक दुवा प्रदान करा. diff --git a/translation/dest/site/ms-MY.xml b/translation/dest/site/ms-MY.xml index 77bfc09f23cbd..52c4166b4daf6 100644 --- a/translation/dest/site/ms-MY.xml +++ b/translation/dest/site/ms-MY.xml @@ -421,9 +421,6 @@ analisis komputer, perbualan dalam permainan dan URL kongsi bersama. Pilih nama yang selamat untuk pertandingan ini. Sebarang perkara yang tidak senonoh boleh menyebabkan akaun anda ditutup. Biarkan kosong untuk menamakan pertandingan kepada pemain catur terkenal. - Kami menasihat agar tidak menukar tetapan ini. - Jika anda tetapkan keperluan kemasukan, pertandingan anda akan ada lebih sedikit pemain. - Tunjukkan tetapan lanjutan Buat pertandingan privasi peribadi and kurangkan akses dengan kata kunci Serta Tarik diri @@ -462,8 +459,6 @@ analisis komputer, perbualan dalam permainan dan URL kongsi bersama. Jika tiada, biar kosong Profil Sunting profile - Nama yang diberi - Nama keluarga Biografi Terima kasih! Pautan media sosial @@ -498,9 +493,7 @@ analisis komputer, perbualan dalam permainan dan URL kongsi bersama. Sebab Apa masalahnya? Penipuan - Unsur ejekan Unsur jenaka - Manipulasi Taraf Lain-lain Laporkan link kepada permainan dan terangkan apa yang dilakukan oleh pengguna yang tidak betul. Jangan hanya beritahu \"mereka menipu\" tapi terangkan juga bagaimana anda dapatkan konklusi ini. Laporan anda akan diproses lebih cepat jika ditulis dalam English. Tolong berikan sekurangnya satu link kepada game yang dicurigai ditipu. diff --git a/translation/dest/site/nb-NO.xml b/translation/dest/site/nb-NO.xml index c1a31af2191f6..dc61f5eb832fc 100644 --- a/translation/dest/site/nb-NO.xml +++ b/translation/dest/site/nb-NO.xml @@ -70,8 +70,8 @@ Forfrem variant Gjør til hovedvariant Slett herfra - Skjul varianter - Vis varianter + Skjul varianter + Vis varianter Vis som variant Kopier variant-PGN Trekk @@ -467,9 +467,6 @@ Velg et veldig trygt navn for turneringen. Upassende innhold kan føre til at brukerkontoen din blir stengt. La stå tomt for at turneringa skal få navn etter en tilfeldig stormester. - Vi anbefaler å ikke endre disse. - Om du stiller betingelser for å delta vil turneringa få færre deltakere. - Vis avanserte innstillinger Gjør turneringen privat og begrens tilgang med et passord Bli med Trekk deg @@ -509,8 +506,7 @@ Hvis ingen, la stå tom Profil Rediger profil - Fornavn - Etternavn + Virkelig navn Velg flair Flair Det finnes en innstilling for å skjule alle brukerflairer på hele nettstedet. @@ -557,9 +553,7 @@ Årsak Hva gjelder det? Juks - Fornærmelse Troll - Ratingmanipulering Annet Kopier lenken til partiet/partiene og forklar hva som er galt med denne brukerens oppførsel. Oppgi minst én lenke til et jukseparti. @@ -594,6 +588,7 @@ Sakte Innenfor brettet Utenfor brettet + Alle felt på brettet På lange partier Alltid Aldri diff --git a/translation/dest/site/ne-NP.xml b/translation/dest/site/ne-NP.xml index 343f61a2b8df1..ebac23c994e0a 100644 --- a/translation/dest/site/ne-NP.xml +++ b/translation/dest/site/ne-NP.xml @@ -438,9 +438,6 @@ प्रतियोगिताको लागि एउटा असाध्यै सभ्य नाम रोजौं। थोरै पनि अवैधानिक भएमा तपाइको खाता नै बन्द गर्न सकिनेछ। खाली राखेमा कुनै ग्र्याण्डमास्टरको नाम आफै राखिनेछ। - यीनलाई नछुने सल्लाह दिईन्छ। - यस्ता प्रावधानहरू राख्दा प्रतियोगितामा खेलाडीको संख्या कम हुन जान्छ। - उन्नत सेटिङहरू देखाऔं प्रतियोगितालाई निजी बनाई पासवर्ड चाहिने बनाऔँ भाग लिनुहोस् फिर्ता @@ -475,8 +472,6 @@ नभएको खण्डमा खाली छोडौं प्रोफाइल प्रोफाइल सम्पादन गर्नुहोस् - शुभनाम - थर जीवनी धन्यवाद! सामाजिक सञ्जाल लिङ्क @@ -513,7 +508,6 @@ कारण समस्या के हो? चोरी - मानहानी ट्रोल अन्य सबै खेलको लिङ्क पेष्ट गरी खेलाडीको व्यवहारमा के गलत छ र तपाई त्यो निष्कर्षमा कसरी पुग्नुभयो उल्लेख गर्नुहोस्। अंग्रेजी भाषाको प्रयोग गर्नुभयो भने छिटो निष्कर्षमा पुग्न सकिनेछ। diff --git a/translation/dest/site/nl-NL.xml b/translation/dest/site/nl-NL.xml index 6de9fd1f1ec8c..31ff9c7ea460a 100644 --- a/translation/dest/site/nl-NL.xml +++ b/translation/dest/site/nl-NL.xml @@ -70,8 +70,8 @@ Promoveer variant Maak hoofdvariant Verwijder vanaf hier - Varianten verbergen - Varianten weergeven + Varianten verbergen + Varianten weergeven Forceer variatie Kopieer variatie PGN Zet @@ -466,9 +466,6 @@ Kies een zeer veilige naam voor het toernooi. Bij zelfs het minste vermoeden van een misplaatst gekozen naam, kan uw account gesloten worden. Laat het veld leeg om het toernooi naar een willekeurige grootmeester te vernoemen. - Wij raden aan om deze niet aan te raken. - Als u voorwaarden voor deelname instelt, zullen er minder spelers meedoen. - Toon uitgebreide instellingen >> Maak het toernooi privé en beperk de toegang met een wachtwoord Neem deel Trek terug @@ -508,8 +505,7 @@ Leeg indien n.v.t. Profiel Pas profiel aan - Voornaam - Achternaam + Echte naam Stel je flair in Symbool Er bestaat een instelling om alle gebruikersflairs over de hele site te verbergen. @@ -556,9 +552,7 @@ Reden Wat is er aan de hand? Valsspelen - Beledigen Provoceren - Rating manipulatie Anders Plak de link naar de partij(en) en leg uit wat er mis is met het gedrag van de gebruiker. Zeg niet alleen \'hij speelt vals\', maar vertel ons hoe u bent gekomen op deze conclusie. Uw rapportage zal sneller worden verwerkt als het in het Engels is geschreven. Geef ten minste één link naar een partij waarin vals gespeeld is. @@ -593,6 +587,7 @@ Traag Binnen het bord Buiten het bord + Alle velden van het bord Alleen bij langzame partijen Altijd Nooit diff --git a/translation/dest/site/nn-NO.xml b/translation/dest/site/nn-NO.xml index 64a80714a7d24..27ba66009450c 100644 --- a/translation/dest/site/nn-NO.xml +++ b/translation/dest/site/nn-NO.xml @@ -468,9 +468,6 @@ få en computeranalyse, chatte eller dele ein URL. Finn eit svært sikkert namn på turneringa. Upassande innhald kan føre til at brukarkontoen din blir stengd. Lat stå tomt for at turneringa skal få namn etter ein tilfeldig stormester. - Vi tilrår å ikkje endre desse. - Om du set opp vilkår for deltaking vil turneringa di få færre deltakarar. - Vis avanserte innstillingar Gjer turneringa privat og avgrens tilgang vha. eit passord Bli med Trekk deg @@ -510,8 +507,7 @@ få en computeranalyse, chatte eller dele ein URL. Om ingen, lat stå tomt Profil Rediger profilen - Førenamn - Etternamn + Verkelege namn Vel eit ikon som best representerer talentet ditt Talent Det finst ei innstilling for å skjule symbolet for spelarane sine talent på heile nettstaden. @@ -558,9 +554,7 @@ få en computeranalyse, chatte eller dele ein URL. Årsak Kva gjeld saka? Juks - Ærekrenking Troll - Ratingmanipulering Anna Lim inn link til partiet/partia og forklar kva som er gale med åtferda til denne brukaren. Oppgje minst ei lenke til eit jukseparti. @@ -595,6 +589,7 @@ få en computeranalyse, chatte eller dele ein URL. Sakte På brettet Utanfor brettet + Alle felt på brettet I parti med lang tid Alltid Aldri diff --git a/translation/dest/site/or-IN.xml b/translation/dest/site/or-IN.xml index e23819d422dad..bcba6566aaaa4 100644 --- a/translation/dest/site/or-IN.xml +++ b/translation/dest/site/or-IN.xml @@ -194,15 +194,11 @@ ଖେଳାଳି ଏକ ନୂଆ ଟୁର୍ନାମେଣ୍ଟ ସୃଷ୍ଟି କରନ୍ତୁ ଵିକଶିତ ସେଟିଂ - ଏଗୁଡ଼ିକୁ ସ୍ପର୍ଶ ନକରିବାକୁ ଆମେ ସୁପାରିଶ କରୁ। - ଯଦି ଆପଣ ପ୍ରବେଶ ଆବଶ୍ୟକତାକୁ ସେଟ୍ କରନ୍ତି, ତେବେ ଆପଣଙ୍କ ଟୁର୍ନାମେଣ୍ଟରେ କମ୍ ଖେଳାଳି ରହିବେ। - ଵିକଶିତ ସେଟିଂ ଦେଖାଅ ବୋର୍ଡ ସମ୍ପାଦକ ଘରୋଇ ପ୍ରୋଫାଇଲ୍ ସମାପ୍ତି: %s ପ୍ରୋଫାଇଲ୍ ପ୍ରୋଫାଇଲ୍ ସମ୍ପାଦନା - ପ୍ରଦତ୍ତ ନାମ ଜୀବନୀ ଧନ୍ୟବାଦ! ପ୍ରତି ଧାଡ଼ିରେ ଗୋଟିଏ URL। diff --git a/translation/dest/site/os-SE.xml b/translation/dest/site/os-SE.xml index 1453652f07d2a..26db56ed8cd22 100644 --- a/translation/dest/site/os-SE.xml +++ b/translation/dest/site/os-SE.xml @@ -392,9 +392,6 @@ Равзар æдас ном турнирæн. Турниры ном куы зæрдæхсайгæ уа, уæд блокадæ дын акæндзысты. Ныууадз афтидæй, цæмæй турнир уыд сæмбæлгæ гроссмейстеры номæн. - Фæлтау сæ ма æвнал. - Хайады домæнтæ куы раттдзынæ, уæд дæ ерысы къаддæр адæм хъаздзысты. - Равдисын баххæстæг настройкæтæ Турнир æхгæдæй скæнын æмæ бацæуæн арæн скæнын паролæй Хайад исын Ныууадзын @@ -429,8 +426,6 @@ Кæд нæй, ныууадз афтидæй Профиль Профиль æндæр кæнын - Ном - Мыггаг Махи тыххæй Бузныг! Социалон хызтæм дæнцæгтæ @@ -469,9 +464,7 @@ Аххос Цы хабар у? Фæлитой митæ - Бафхæрд Троллинг - Рейтингимæ махинацитæ Æндæр Бавæр дæнцæгтæ ахæм хъæзтытæм, кæм, дæм кæсы, уыдысты уагты халынтæ, æмæ бамбарын кæ, цы хабар у. Комкоммæ зæгъын \"уый сайы\" æнæфаг у, радзур нын, ахæм хатдзæгмæ куыд æрцыдтæ. Мах тагъддæр кусдзыстæм, англисагау кæд фысдзынæ. Табуафси, бафтау æммыст иу дæнцæг ахæм хъазтмæ, кæм, дæм кæсы, уыдысты уагты халынтæ. diff --git a/translation/dest/site/pl-PL.xml b/translation/dest/site/pl-PL.xml index bb9d250caf713..c2687e9602c72 100644 --- a/translation/dest/site/pl-PL.xml +++ b/translation/dest/site/pl-PL.xml @@ -533,9 +533,6 @@ Nadaj turniejowi neutralną nazwę. Nawet nieco nieodpowiednia nazwa może spowodować zamknięcie Twojego konta. Jeśli nie podasz nazwy turnieju, zostanie on nazwany od nazwiska losowo wybranego arcymistrza. - Zalecamy nie zmieniać tych ustawień. - Ustawienie warunków wstępu sprawi, że do turnieju przystąpi mniej osób. - Pokaż ustawienia zaawansowane Ustaw turniej jako prywatny i ogranicz dostęp za pomocą hasła Dołącz Wycofaj się @@ -575,8 +572,7 @@ Jeśli nie masz, zostaw puste Profil Edycja profilu - Imię - Nazwisko + Prawdziwe nazwisko Ustaw swój emblemat Emblemat Istnieje ustawienie, pozwalające ukryć wszystkie emblematy użytkowników na lichess. @@ -625,9 +621,7 @@ Powód Jakie to ma znaczenie? Oszust - Znieważenie Natręt (troll) - Manipulacja rankingiem Inne Wklej odnośnik do partii i wyjaśnij, co złego jest w zachowaniu tego użytkownika. Nie pisz tylko, że „oszukuje”, ale wytłumacz nam, na jakiej podstawie doszedłeś/aś do takiego wniosku. Odniesiemy się do twojego zgłoszenia szybciej, jeżeli napiszesz je w języku angielskim. Podaj przynajmniej jeden odnośnik do gry, w której oszukiwano. @@ -662,6 +656,7 @@ Wolno Na szachownicy Poza szachownicą + Wszystkie pola szachownicy W wolniejszych partiach Zawsze Nigdy diff --git a/translation/dest/site/pt-BR.xml b/translation/dest/site/pt-BR.xml index 705434a0b1373..0200a37228fc3 100644 --- a/translation/dest/site/pt-BR.xml +++ b/translation/dest/site/pt-BR.xml @@ -467,9 +467,6 @@ Escolha um nome seguro para o torneio. Até mesmo a menor indecência poderia ensejar o encerramento de sua conta. Deixe em branco para dar ao torneio o nome de um grande mestre aleatório. - Recomendamos não mexer nisso. - Se você restringir as condições de participação, haverá menos jogadores no torneio. - Exibir configurações avançadas Faça o torneio privado e restrinja o acesso com uma senha Entrar Sair @@ -509,8 +506,7 @@ Se nenhuma, deixe vazio Perfil Editar perfil - Primeiro nome - Sobrenome + Nome real Escolha seu emote Estilo Você pode esconder todos os emotes de usuário no site. @@ -557,9 +553,7 @@ Motivo Qual é o motivo? Trapaça - Insulto Troll - Manipulação de rating Outro Cole o link do(s) jogo(s) e explique o que há de errado com o comportamento do usuário. Não diga apenas \"ele trapaceia\", informe-nos como chegou a esta conclusão. Sua denúncia será processada mais rapidamente se escrita em inglês. Por favor forneça ao menos um link para um jogo com suspeita de trapaça. @@ -594,6 +588,7 @@ Lento Dentro do tabuleiro Fora do tabuleiro + Todas as casas do tabuleiro Em partidas lentas Sempre Nunca diff --git a/translation/dest/site/pt-PT.xml b/translation/dest/site/pt-PT.xml index b6e72e924705f..1557a2e6d3731 100644 --- a/translation/dest/site/pt-PT.xml +++ b/translation/dest/site/pt-PT.xml @@ -468,9 +468,6 @@ análise de computador, sala de chat do jogo e link de partilha. Escolhe um nome totalmente seguro para o torneio. Até uma linguagem ligeiramente inadequada pode levar ao encerramento da tua conta. Deixe em branco e será atribuído um nome aleatório de um jogador notável ao torneio. - Recomendamos que não mechas nestas oções. - Se definies condições de participação, o teu torneio terá menos jogadores. - Mostrar as definições avançadas Torna o torneio privado e restrinje o acesso com uma palavra-passe Entrar Sair @@ -510,8 +507,7 @@ análise de computador, sala de chat do jogo e link de partilha. Se não existir, deixa em branco Perfil Editar o perfil - Nome próprio - Apelido + Nome Real Defina o teu estilo Estilo Há uma opção para ocultar todos os estilos dos utilizadores em todo o site. @@ -558,9 +554,7 @@ análise de computador, sala de chat do jogo e link de partilha. Motivo Qual é o motivo? Batota - Insulto Troll - Manipulação do Elo Outro Inclui o link do(s) jogo(s) e explica o que há de errado com o comportamento deste utilizador. Não digas apenas \"ele faz batota\"; informa-nos como chegaste a essa conclusão. A tua denúncia será processada mais rapidamente se for escrita em inglês. Por favor, fornece-nos pelo menos um link para um jogo onde tenha havido batota. @@ -595,6 +589,7 @@ análise de computador, sala de chat do jogo e link de partilha. Lento Dentro do tabuleiro Fora do tabuleiro + Todas as casas do tabuleiro Em jogos lentos Sempre Nunca diff --git a/translation/dest/site/ro-RO.xml b/translation/dest/site/ro-RO.xml index 1aea4d893578d..77b1278e2097a 100644 --- a/translation/dest/site/ro-RO.xml +++ b/translation/dest/site/ro-RO.xml @@ -498,9 +498,6 @@ Alege un nume foarte sigur pentru turneu. Orice nume care este chiar și ușor nepotrivit poate cauza închiderea contului tău. Lăsați necompletat pentru a numi turneul după un jucător bun de șah. - Iți recomandăm să nu modifici aceste setări. - Dacă stabilești condiții de intrare, turneul tău va avea mai puțini jucători. - Afișează setările avansate Faceți competiția privată și restricționați accesul cu o parolă Intră Retrage-te @@ -540,8 +537,7 @@ Dacă nu există, nu completați Profil Editează profilul - Prenume - Nume + Nume real Pictograma personalizată Biografie Țara sau regiunea @@ -586,9 +582,7 @@ Motiv Care este problema? Trișează - Insultă Troll - Manipularea evaluării Altceva Adaugă link-ul de la joc(uri) și arată ce este greșit cu privire la acest comportament al utilizatorului. Nu preciza doar ”trișează”, ci spune-ne cum ai ajuns la această concluzie. Raportul tău va fi procesat mai rapid dacă este scris în engleză. Te rugăm să furnizezi cel puțin un link către un joc în care s-a trișat. diff --git a/translation/dest/site/ru-RU.xml b/translation/dest/site/ru-RU.xml index 97db45e465e21..e6a96b18d12aa 100644 --- a/translation/dest/site/ru-RU.xml +++ b/translation/dest/site/ru-RU.xml @@ -533,9 +533,6 @@ Выберите для турнира как можно более безопасное название. Если название хотя бы немного покажется неуместным, вас могут заблокировать. Оставьте пустым, чтобы назвать турнир в честь случайного гроссмейстера. - Мы не рекомендуем задавать эти условия. - Если вы зададите условия участия, то в вашем турнире сможет принять участие меньше игроков. - Показать дополнительные настройки Сделать турнир закрытым и ограничить доступ паролем Участвовать Покинуть @@ -575,8 +572,6 @@ Если нет, оставьте пустым Профиль Редактировать профиль - Имя - Фамилия Задайте свой эмодзи Эмодзи Эта настройка скрывает все эмодзи пользователей на всём сайте. @@ -625,9 +620,7 @@ Причина Что это было? Жульничество - Оскорбление Троллинг - Махинации с рейтингом Другое Поделитесь с нами ссылками на игры, где, как вам кажется, были нарушены правила, и опишите, в чём дело. Недостаточно просто написать «он мухлюет», пожалуйста, опишите, как вы пришли к такому выводу. Мы сработаем оперативнее, если вы напишете на английском языке. Пожалуйста, добавьте ссылку хотя бы на одну игру, где по вашему мнению были нарушены правила. diff --git a/translation/dest/site/ry-UA.xml b/translation/dest/site/ry-UA.xml index cf0068068e27a..8dbcab2447c67 100644 --- a/translation/dest/site/ry-UA.xml +++ b/translation/dest/site/ry-UA.xml @@ -429,7 +429,6 @@ Триманя Побідник Турнірна табела - Кідь покладете условія, менше зможе бавити на турнірови. Прикапчати ся Лишити Балув @@ -444,7 +443,6 @@ Закрытый Рейтінґ %s Профіл - Прозвище За ся Захарити ходы Успіх @@ -471,7 +469,6 @@ Хосновач Причина Чітерство - Оскорба Тролінґ Иншакоє Подїліт ся из нами одкликованями на бавкы, де вам ся видит, были нарушені правила, ай опишіт, у чому дїло. Не доста просто написати \"ун чітер\", уповічте чому сьте так рішили. Наріканя достане одвіт скорше, кідь оно на анґлійськув бисідї. diff --git a/translation/dest/site/sco-GB.xml b/translation/dest/site/sco-GB.xml index 2be0b84d6f4b8..ebb9e7f81180e 100644 --- a/translation/dest/site/sco-GB.xml +++ b/translation/dest/site/sco-GB.xml @@ -310,9 +310,6 @@ Wale a gey mensefu name for the kemp. E\'en o barelins menseless cuild gar yer accoont ill end. Let abe tae caw the kemp efter a faur-kent chess pleyyer. - We rede ye no titch thir. - If ye set entry perconnons, yer kemp maun gaither fewer pleyyers. - Shaw pernicketie sets Mak the kemp preevat, an stent access wae a trystwird Jine Resile @@ -340,9 +337,7 @@ Raison Whit\'s wrang? Cleek - Lichtlie Fash - Ratin maneepulatit Ither Paste the link tae the gemm(s) an expone whit\'s wrang anent this uiser\'s behaviour. Dinnae juist say \"they cleek\", but tell us whitwey ye\'d cam tae this end. Yer clype will be processit faster gin scrievit wae English. Please provide least ane line tae a cleekit gemm. diff --git a/translation/dest/site/si-LK.xml b/translation/dest/site/si-LK.xml index a0d17df83072d..a95bfa55f2b69 100644 --- a/translation/dest/site/si-LK.xml +++ b/translation/dest/site/si-LK.xml @@ -417,9 +417,6 @@ You are playing chess. තරඟාවලිය සඳහා ඉතා ආරක්ෂිත නමක් තෝරන්න. තරමක් නුසුදුසු ඕනෑම දෙයක් නිසා ඔබගේ ගිණුම වසා දැමිය හැක. කැපී පෙනෙන චෙස් ක්‍රීඩකයෙකුගේ නමින් තරඟාවලිය නම් කිරීමට හිස්ව තබන්න. - මේවා ඇල්ලීම​ නොකරන ලෙස අපි නිර්දේශ කරමු. - ඔබ ප්‍රවේශ අවශ්‍යතා සකසන්නේ නම්, ඔබේ තරඟාවලියට ක්‍රීඩකයින් සංඛ්‍යාව අඩු වනු ඇත. - උසස් සැකසුම් පෙන්වන්න තරඟාවලිය පුද්ගලික කරන්න, මුරපදයකින් ප්‍රවේශය සීමා කරන්න එකතු වන්න​ ඉල්ලා අස් වෙන්න​ @@ -459,8 +456,6 @@ You are playing chess. කිසිවක් නොමැති නම්, හිස්ව තබන්න පැතිකඩ පැතිකඩ සංස්කරණය කරන්න - මුල් නම - වාසගම ඔබේ ෆ්ලෙයාර්ය​ සකසන්න ෆ්ලෙයාරය සම්පූර්ණ වෙබ් අඩවිය හරහා සියලුම පරිශීලක ෆ්ලෙයාර සැඟවීමට සැකසීමක් ඇත. @@ -507,9 +502,7 @@ You are playing chess. හේතුව කාරණය කුමක් ද? වංචාකරණ​ය​ - අපහාස කිරීම​ ට්‍රෝල් කිරීම​ - ශ්‍රේණිගත කිරීම් වංචාව​ වෙනත් ක්‍රීඩා(ව​) වෙත සබැඳිය අලවා මෙම පරිශීලකයාගේ හැසිරීමේ ඇති වරද කුමක්දැයි පැහැදිලි කරන්න. \"ඔවුන් වංචා කරනවා\" පමණක් නොකියන්න, නමුත් ඔබ මෙම නිගමනයට පැමිණියේ කෙසේදැයි අපට කියන්න. ඔබේ වාර්තාව ඉංග්‍රීසියෙන් ලියා ඇත්නම් වේගයෙන් සකසනු ලැබේ. වංචා කළ ක්‍රීඩාව සඳහා එක් සබැඳියක්වත් දීමට කාරුණික වන්න​. diff --git a/translation/dest/site/sk-SK.xml b/translation/dest/site/sk-SK.xml index f7eb9ef00fe9f..426d9c1ad92c3 100644 --- a/translation/dest/site/sk-SK.xml +++ b/translation/dest/site/sk-SK.xml @@ -532,9 +532,6 @@ Názov turnaja vyberajte s rozvahou! Čokoľvek čo i len mierne nevhodné môže mať za následok uzavretie Vášho účtu. Ak názov nevyplníte, turnaj bude pomenovaný po náhodnom veľmajstrovi. - Tieto nastavenia odporúčame ponechať nedotknuté. - Ak nastavíte podmienky účasti, Váš turnaj bude mať menej hráčov. - Zobraziť pokročilé nastavenia Nastav turnaj ako súkromný a obmedz prístup k nemu pomocou hesla Pripojiť Odstúpiť @@ -574,8 +571,7 @@ Ak nemáte, nevyplňujte Profil Upraviť profil - Meno - Priezvisko + Skutočné meno Nastavte si svoju ikonku štýlu Ikonka štýlu V nastaveniach je možné skryť všetky ikonky štýlu používateľov na celej stránke. @@ -623,9 +619,7 @@ Dôvod Čo sa deje? Podvod - Urážka Troll - Ovplyvňovanie ratingu Iné Vložte odkaz na hru/y, a vysvetlite, čo je zlé na tomto správaní používateľa. Prosím, uveďte aspoň jeden odkaz na partiu, v ktorej sa podvádzalo. @@ -660,6 +654,7 @@ Pomaly Vnútri šachovnice Mimo šachovnice + Všetky políčka šachovnice Pri pomalých hrách Vždy Nikdy diff --git a/translation/dest/site/sl-SI.xml b/translation/dest/site/sl-SI.xml index 45b0341f50a14..434cf8d83832d 100644 --- a/translation/dest/site/sl-SI.xml +++ b/translation/dest/site/sl-SI.xml @@ -532,9 +532,6 @@ Izberite zelo varno ime za turnir. Že zelo majhna neprimernost lahko povzroči ukinitev vašega računa. Pustite prazno, da bo turnir imenovan po naključnem velemojstru. - Priporočamo, da se tega ne dotikate. - Če nastavite pogoje za vstop, bo imel vaš turnir manj igralcev. - Prikaži napredne nastavitve Naredi turnir zaseben in zaščiti dostop z geslom Pridruži se Zapusti turnir @@ -574,8 +571,6 @@ Če ni, pustite prazno Profil Uredi profil - Ime - Priimek Določite svoj okus Simbol Obstaja nastavitev za skrivanje vseh uporabniških čustev na celotnem spletnem mestu. @@ -624,9 +619,7 @@ Razlog Kaj je narobe? Goljufija - Žalitev Provokacija - Manipulacija z ratingi Drugo Prilepite povezave do igre (ali iger) in pojasnite kaj je narobe z obnašanjem uporabnika. Ne napišite samo \"uporabnik goljufa\" temveč pojasnite zakaj mislite tako. Prijava bo obdelana hitreje če bo napisana v angleščini. Navedite vsaj eno povezavo do igre s primerom goljufanja. diff --git a/translation/dest/site/sq-AL.xml b/translation/dest/site/sq-AL.xml index 81ebc24c7f7be..18b90e7552ea6 100644 --- a/translation/dest/site/sq-AL.xml +++ b/translation/dest/site/sq-AL.xml @@ -468,9 +468,6 @@ loje dhe URL për ta ndarë me të tjerë. Zgjidhni një emër shumë të sigurt për turneun. Çfarëdo gjëje qoftë edhe pakëz e papërshtatshme mund të sjellë mbylljen e llogarisë tuaj. Për ta emërtuar turneun me emrin e një lojtari të njohur shahu, lëreni të zbrazët. - Rekomandojmë të mos i prekni këto. - Nëse ujdisni domosdoshmëri pjesëmarrjeje, turneu juaj do të ketë më pak lojtarë. - Shfaq rregullimet të thelluara Bëjeni turneun privat dhe kufizojeni hyrjen me një fjalëkalim Merrni pjesë Tërhiquni @@ -509,8 +506,7 @@ loje dhe URL për ta ndarë me të tjerë. Në mos pastë, lëreni të zbrazët Profil Përpunoni profilin - Emër - Mbiemër + Emër i njëmendtë Jetëshkrim Vend ose rajon Faleminderit! @@ -554,9 +550,7 @@ loje dhe URL për ta ndarë me të tjerë. Arsye Si është puna? Hile - Fyerje Troll - Manipulim vlerësimi Tjetër Ngjitni lidhjen për te loja(ra) dhe shpjegoni çfarë nuk shkon me sjelljen e këtij përdoruesi. Mos shkruani thjesht “mashtrojnë”, por na tregoni si mbërritët në këtë përfundim. Raportimi juaj do të përpunohet më shpejt, nëse shkruhet në anglisht. Ju lutemi, jepni të paktën një lidhje te një lojë me hile. @@ -591,6 +585,7 @@ loje dhe URL për ta ndarë me të tjerë. Ngadalë Brenda fushës Jashtë fushës + Në krejt katrorët e fushës Në lojëra të ngadalta Përherë Kurrë diff --git a/translation/dest/site/sr-SP.xml b/translation/dest/site/sr-SP.xml index 301b6ca9a1575..722949d859423 100644 --- a/translation/dest/site/sr-SP.xml +++ b/translation/dest/site/sr-SP.xml @@ -474,9 +474,6 @@ Изаберите веома сигурно име за турнир. Било које име које је бар мало неприкладно може да вам затвори налог. Оставите празно како би назвали турнир по насумично изабраном Велемајстору. - Препоручујемо да немењате ове. - Ако ставите услове за улаз, ваш турнир ће имати мање играча. - Покажи напредна подешавања Учини турнир приватним и ограничи приступ са шифром Придружи се Повуци се @@ -516,8 +513,6 @@ Ако немате рејтинг, оставите празно Профил Уредите профил - Име - Презиме Постави своју значку Значка Постоји подешавање којим се сакривају све корисникове значке на читавом сајту. @@ -565,9 +560,7 @@ Разлог Y чему је проблем? Варање - Увреда Трол - Манипулација рејтингом Остало Залијепите везу до игре и објасните шта није у реду са понашањем корисника. Немојте само рећи \"варао\", али реците како сте дошли до тог закључка. Ваша пријава ће бити обрађена брже ако је напишете на енглеском језику. Наведите барем једну везу игре у којој је играч варао. diff --git a/translation/dest/site/sv-SE.xml b/translation/dest/site/sv-SE.xml index e302fa1d8f27a..b7724b7fa146c 100644 --- a/translation/dest/site/sv-SE.xml +++ b/translation/dest/site/sv-SE.xml @@ -464,9 +464,6 @@ Välj ett mycket säkert namn för turneringen. Någonting, om än bara lite, olämpligt skulle kunna få ditt konto att stängas. Lämna tom för att namnge turneringen efter en slumpmässig stormästare. - Vi rekommenderar att inte röra dessa. - Om du anger inträdesvillkor kommer din turnering att ha färre spelare. - Visa avancerade inställningar Gör turneringen privat, och begränsa åtkomst med ett lösenord Delta Lämna @@ -506,8 +503,7 @@ Om ingen, lämna tomt Profil Ändra profil - Förnamn - Efternamn + Verkligt namn Ställ in din flair Flair Det finns en inställning för att dölja alla användarflairs över hela webbplatsen. @@ -554,9 +550,7 @@ Anledning Vad är problemet? Fusk - Förolämpning Troll - Manipulation av rating Annat Klistra in länken till partiet och förklara vad som är fel med den här användarens beteende. Säg inte bara \"de fuskar\", utan förklara hur du dragit denna slutsats. Din rapport kommer att behandlas fortare om den är skriven på engelska. Ange minst en länk till ett spel där användaren fuskade. diff --git a/translation/dest/site/ta-IN.xml b/translation/dest/site/ta-IN.xml index accd138ff7c49..a0e53d8f46fe9 100644 --- a/translation/dest/site/ta-IN.xml +++ b/translation/dest/site/ta-IN.xml @@ -449,9 +449,6 @@ போட்டிக்கு மிகவும் பாதுகாப்பான பெயரைத் தேர்ந்தெடுக்கவும். சற்று பொருத்தமற்றது கூட உங்கள் கணக்கை மூடக்கூடும். ஒரு குறிப்பிடத் தக்க சதுரங்க வீரரின் பெயரைப் போட்டிக்குப் பெயரிட காலியாக விடவும். - இவற்றில் மாற்றம் செய்யவேண்டாமெனப் பரிந்துரைக்கின்றோம். - நீங்கள் போட்டியாளர்களுக்கான நிபந்தனைகளை உருவாக்கினால், உங்கள் போட்டிகளில் குறைவான வீரர்களே பங்குபற்றுவார்கள். - மேம்பட்ட அமைப்புகளைப் பார்க்கவும் போட்டியைத் தனிப்பட்டதாக்க, கடவுச்சொல் கொண்டு அணுக்கத்தைக் கட்டுப்படுத்தவும் சேர் விலகு @@ -491,8 +488,6 @@ இல்லையென்றால் காலியாக விடலாம் குறிப்புகள் தகவல்களை மாற்று - முதல் பெயர் - கடைசி பெயர் உங்கள் திறமையை அமைக்கவும் தளம் முழுவதும் அனைத்து பயனர் திறமைகளையும் மறைக்க ஒரு அமைப்பு உள்ளது. வாழ்க்கை சரித்திரம் @@ -537,9 +532,7 @@ காரணம் என்ன விஷயம்? வஞ்சகன் - அவமதிப்பு அக்கிரமி - மதிப்பீட்டைச் சூழ்ச்சியுடன் திரித்தல் வேறு விளையாட்டு (கள்) இணைப்பை ஒட்டவும் மேலும் இந்த பயனர் நடத்தை பற்றி தவறு என்ன என்பதை விளக்க. வெறும் \"அவர்கள் ஏமாற்றுகின்றனர்\" சொல்ல வேண்டாம், ஆனால் நீங்கள் இந்த முடிவுக்கு வந்தது எப்படி எங்களுக்கு சொல்ல. ஆங்கிலத்தில் எழுதப்பட்ட என்றால் உங்கள் அறிக்கை வேகமாக செயல்படுத்தப்படும். ஏமாற்றப்பட்ட விளையாட்டிற்கு குறைந்தபட்சம் ஒரு இணைப்பை வழங்கவும். diff --git a/translation/dest/site/te-IN.xml b/translation/dest/site/te-IN.xml index 6b765aa214823..9397e6bc2c7a0 100644 --- a/translation/dest/site/te-IN.xml +++ b/translation/dest/site/te-IN.xml @@ -394,9 +394,6 @@ టోర్నమెంట్ కోసం చాలా సురక్షితమైన పేరును ఎంచుకోండి. కొంచెం అనుచితమైన ఏదైనా మీ ఖాతా మూసివేయబడుతుంది. చెస్ ఆటగాడి పేరు మీద టోర్నమెంట్ పేరు పెట్టడానికి ఖాళీగా ఉండండి. - వీటిని తాకవద్దని మేము సిఫార్సు చేస్తున్నాము. - మీరు ప్రవేశ అవసరాలను సెట్ చేస్తే, మీ టోర్నమెంట్‌లో తక్కువ మంది ఆటగాళ్ళు ఉంటారు. - అధునాతన సెట్టింగ్‌లను చూపించు టోర్నమెంట్‌ను ప్రైవేట్‌గా చేయండి మరియు పాస్‌వర్డ్‌తో ప్రాప్యతను పరిమితం చేయండి చేరండి ఉపసంహరించుకోండి @@ -433,8 +430,6 @@ ఏదీ లేకుంటే, ఖాళీగా వదిలివేయండి ప్రొఫైల్ ప్రొఫైల్ మార్చాలి - ఇచ్చిన పేరు - ఇంటిపేరు జీవిత చరిత్ర ధన్యవాదాలు! సోషల్ మీడియా లింక్‌లు @@ -474,9 +469,7 @@ కారణం మీరు ఎందుకు రిపోర్టు చేస్తున్నారు? మోసం - అవమానం ఎగతాళి చేయండి - రేటింగ్ మానిప్యులేషన్ ఇతర గేమ్/గేమ్‌ల లింక్‌ను పంపండి మరియు ఈ వినియోగదారు ప్రవర్తనలో తప్పు ఏమిటో వివరించండి. \"వారు మోసం చేసారు\" అని చెప్పకండి, కానీ మీరు ఈ నిర్ణయానికి ఎలా వచ్చారో మాకు చెప్పండి. మీ నివేదికను ఆంగ్లంలో వ్రాస్తే వేగంగా ప్రాసెస్ చేయబడుతుంది. దయచేసి కనీసం మోసం చేసిన ఆట ఒక లింక్‌నైనౌ అందించండి. diff --git a/translation/dest/site/th-TH.xml b/translation/dest/site/th-TH.xml index b328f6e008a10..784bbc5fe4060 100644 --- a/translation/dest/site/th-TH.xml +++ b/translation/dest/site/th-TH.xml @@ -432,9 +432,6 @@ เลือกชื่อที่ปลอดภัยสุดสำหรับทัวร์นาเมนต์ อะไรที่ไม่เหมาะสมแม้เพียงเล็กน้อย ก็สามารถทำให้บัญชีของคุณถูกปิดได้ ปล่อยว่างไว้เพื่อตั้งชื่อทัวร์นาเมนต์หลังจากสุ่มแกรนด์มาสเตอร์ - เราแนะนำว่าอย่าแตะต้องสิ่งเหล่านี้ - ถ้าคุณตั้งค่าเงื่อนไขการเข้าร่วม ทัวร์นาเมนต์ของคุณจะมีผู้เข้าเล่นได้น้อยลง - แสดงการตั้งค่าขั้นสูง ทำทัวร์นาเมนต์ให้เป็นส่วนตัว และจำกัดการเข้าถึงด้วยรหัสผ่าน เข้าร่วม ถอนตัว @@ -474,8 +471,6 @@ ถ้าไม่มี, จงเว้นว่าง ข้อมูลประจำตัว แก้ไขข้อมูลประจำตัว - ชื่อจริง - นามสกุล ตั้งค่ารูปตกแต่ง รูปตกแต่ง มันมีการตั้งค่าที่ทำให้ไม่สามารถเห็นรูปตกแต่งของผู้ใช้ได้ทั้งเวบไซต์ @@ -521,9 +516,7 @@ เหตุผล เกิดอะไรขึ้น? โกง - ดูหมิ่น ป่วน - การปรับแต่งระดับคะแนน อื่นๆ วางลิงค์ที่ไปสู่เกม และอธิบายว่าผู้ใช้นี้มีพฤติกรรมที่ผิดอะไร อย่าบอกเพียงว่า \"พวกเขาโกง\" แต่บอกเราว่าคุณสรุปแบบนี้เพราะอะไร การรายงานของคุณจะเร็วขึ้นถ้าเขียนด้วยภาษาอังกฤษ โปรดระบุอย่างน้อยหนึ่งลิงก์ เพื่อเชื่อมโยงไปยังเกมที่มีการโกง diff --git a/translation/dest/site/tk-TM.xml b/translation/dest/site/tk-TM.xml index 18a47a2938391..5e7fadb5c95b5 100644 --- a/translation/dest/site/tk-TM.xml +++ b/translation/dest/site/tk-TM.xml @@ -362,9 +362,6 @@ kompýuter derňewi, oýun çäti we paýlaşmaga URL alýaňyz. Bäsleşik üçin diýseň howpsuz at saýlaň. Sähelçe bijaý zat hem hasabyňyzyň ýapylmagyna eltip biler. Bäsleşigi çem gelen bir grossmeýster şanyna atlandyrmak üçin bu bölümi boş goýuň. - Muny ellemezligi ündeýäs. - Gatnaşma şertleri goýsaňyz bäsleşigiňize az oýunçy gatnaşar. - Çuňlaşdyrylan sazlamalary görkez Gatnaş Çekil Utuklar @@ -398,8 +395,6 @@ kompýuter derňewi, oýun çäti we paýlaşmaga URL alýaňyz. Ýok bolsa boş goýuň Profil Profili özgert - Ady - Familýasy Biografiýa Minnetdar! Notasiýa şol bir setirde @@ -435,7 +430,6 @@ kompýuter derňewi, oýun çäti we paýlaşmaga URL alýaňyz. Sebäp Näme bolýär? Kezzaplyk - Kemsitme Trol Başga Oýnyň çelgisini üpjün ediň we bu ulanyjynyň özüni alyp barşynda näme ýerliksizlik bardygyny hem düşündiriň. Diňe bir \"ol kezzaplyk edýär\" diýmek bilen çäklenmäň, nädip beýle netijä gelendigiňizem aýdyň. Iňlis dilinde ýazylan bolsa reportyňyz has çalt ýola goýular. diff --git a/translation/dest/site/tl-PH.xml b/translation/dest/site/tl-PH.xml index bbe901f46bdcf..62645128476b5 100644 --- a/translation/dest/site/tl-PH.xml +++ b/translation/dest/site/tl-PH.xml @@ -406,9 +406,6 @@ Pumili ka ng angkop na pangalan ng paligsahan. Maaaring masara ang iyong account kapag naglagay ka ng kahit anong hindi naaangkop na bagay. Pabayaang walang laman upang pangalanan ang paligsahang ito sa isang Grandmaster. - Payo naming huwag pakialaman ang mga ito. - Kung maglagay ka ng mga kondisyon upang makapasok, kakaunti ang mga maglalaro ng iyong paligsahan. - Ipakita ang mga masulong na setting Gawing pribado ang paligsahan, at higpitan ang access sa pamamagitan ng isang password Sumali Umalis @@ -447,8 +444,6 @@ Kung wala, iwang walang laman Profile I-edit ang profile - Pangalan - Apelyido Talambuhay Salamat! Mga link ng social media @@ -490,9 +485,7 @@ Dahilan Ano ang problema? Pandaraya - Insulto Awitin - Pagmamanipula ng rating Iba pa I-paste ang link sa mga laro at ipaliwanag kung ano ang mali tungkol sa ugali ng gumagamit. Huwag mo lamang sabihin na \"sila\'y nandaya\", pero sabihin mo sa amin kung paano mo ito nasabi. Ang iyong report ay ipoproseso ng mas mabilis kapag nakasulat sa English. Magbigay ng kahit na isang link sa dinayang laro. diff --git a/translation/dest/site/tp-TP.xml b/translation/dest/site/tp-TP.xml index a689b1a38132f..30966952a0b6b 100644 --- a/translation/dest/site/tp-TP.xml +++ b/translation/dest/site/tp-TP.xml @@ -446,9 +446,6 @@ o musi lon lipu pona. sina wile ala pana e nimi li wile ala e ilo. sitelen esun o pana e nimi pona tawa utala musi ni. o kepeken e nimi ike, e nimi ni: ona li ike tawa jan ante sina pana e nimi ike tawa utala musi la, lipu lawa sina li ken kama pakala. sina pana ala e nimi la, nimi li kama tan jan musi suli pi musi pi lawa mije moli. - mi toki insa e ni: sina wile ala ante e ona. - utala musi sina li jo e nasin open, jan musi pi mute lili li kama tawa ona. - o pana lukin e ante nasin mute o pana e nimi awen tawa utala musi sina tawa ni: jan pona sina taso li ken kama tawa ona. kama tawa weka @@ -487,8 +484,6 @@ o musi lon lipu pona. sina wile ala pana e nimi li wile ala e ilo. sitelen esun ala li lon la, o sitelen ala lipu jan ante e sona jan - nimi jan - nimi mama sitelen namako sona jan sina pona tawa sina! @@ -530,9 +525,7 @@ o musi lon lipu pona. sina wile ala pana e nimi li wile ala e ilo. sitelen esun tan seme? jan ni li ike seme? musi ike - toki ike pakala e musi - jan li ante ike e nanpa ijo ante o pana e nimi nasin tawa musi. o toki e ni: jan li musi ike lon nasin seme? o toki ala e \"ona li toki kepeken ilo ike\". o toki e ni: sina kama sona e ni tan seme? sina toki kepeken toki Inli la, mi lukin e toki sina lon tenpo lili. o pana e nimi nasin tawa musi pi pali ike. diff --git a/translation/dest/site/tr-TR.xml b/translation/dest/site/tr-TR.xml index b05f639832607..480605f989c43 100644 --- a/translation/dest/site/tr-TR.xml +++ b/translation/dest/site/tr-TR.xml @@ -70,6 +70,8 @@ Bu varyanttan devam et Ana devam yolu yap Bu hamleden sonrasını sil + Varyasyonları daralt + Varyasyonları genişler Varyant olarak göster Varyasyon PGN\'sini kopyala Hamle @@ -465,9 +467,6 @@ Turnuva için oldukça güvenli bir isim seçin. Herhangi bir uygunsuz isim, hesabınızın yasaklanmasına yol açabilir. Kutuyu boş bırakarak turnuvaya rastgele bir Büyükusta\'nın ismini verebilirsiniz. - Bu ayarlara dokunmamanızı öneririz. - Turnuvaya katılım şartı koyarsanız, daha az oyuncu katılacaktır. - Gelişmiş ayarları göster Turnuvayı özel olarak ayarlayın ve şifre koyarak erişimi kısıtlayın Katıl Çekil @@ -507,8 +506,7 @@ Eğer yoksa, boş bırakın Profil Profili düzenle - Ad - Soyad + Gerçek isim Rozetinizi seçin Rozet Ayarlardan oyuncu rozetlerini gizleyebilirsiniz. @@ -555,9 +553,7 @@ Sebep Problem nedir? Hile - Hakaret Trol - Puan manipülasyonu Diğer Raporlamak istediğiniz oyunun linkini yapıştırın ve sorununuzu açıklayın. Lütfen sadece \"hile yapıyor\" gibisinden açıklama yazmayın, hile olduğunu nasıl anladığınızı açıklayın. Rapor edeceğiniz kişiyi ya da oyunu \"İngilizce\" açıklarsanız, daha hızlı sonuca ulaşırsınız. Lütfen hileli gördüğünüz en az 1 adet oyun linki verin. diff --git a/translation/dest/site/tt-RU.xml b/translation/dest/site/tt-RU.xml index f2172f631cc4a..82671c730c73c 100644 --- a/translation/dest/site/tt-RU.xml +++ b/translation/dest/site/tt-RU.xml @@ -368,9 +368,6 @@ Бәйге өчен хуп имин исем куегыз. Әз генә дә килешми торган исем аккаунтыгызның ябылуына китерә ала. Бәйгегә берәр атаклы шаһматчының исемен бирү өчен буш калдырыгыз. - Боларны тимәскә кинәш итәбез. - Бәйгегә керү тәлапләрен куйсагыз, уенчылар саны әзрәк булыр. - Кушылма көйләүләрне күрсәтү Бәйгегә серсүз буенча гына керү куя аласыз Катнашу Уеннан чыгу @@ -407,8 +404,6 @@ Булмаса, буш калдырыгыз Профиль Профильны үзгәртү - Исем - Фамилия Үзең турында Рәхмәт! Социаль медиа сылтамалары @@ -446,9 +441,7 @@ Сәбаб Нәрсә булды? Алдамак - Хурламак Троль - Рейтинг белән уйнамак Бүтән Уен(нар) сылтамаларын кертегез һәм бу кулланучының тәртибендә дөрес булмаганны аңлатыгыз. \"Алдакчы\" дип кенә әйтмәгез, бу нәтиҗәгә ничек килгәнегезне әйтегез. Сезнең җибәрүегез инглизчә язылган очракта тизрәк эшкәртеләчәк. Зинһар, алданган уенга ким дигәндә бер сылтама бирегез. diff --git a/translation/dest/site/uk-UA.xml b/translation/dest/site/uk-UA.xml index 316293925f55b..051f900c72711 100644 --- a/translation/dest/site/uk-UA.xml +++ b/translation/dest/site/uk-UA.xml @@ -72,6 +72,8 @@ Підвищити пріоритет варіанта Зробити варіант основним Видалити з цього місця + Згорнути варіанти + Розгорнути варіанти Зробити варіантом Скопіювати PGN варіанту Хід @@ -531,9 +533,6 @@ Оберіть пристойну назву для турніру. Все, що навіть трохи виявиться недоречним, може призвести до блокування. Залиште пустим, щоб назвати турнір на честь випадкового гросмейстера. - Ми радимо не змінювати їх. - Якщо ви встановите умови вступу, у вашому турнірі буде менше гравців. - Показати додаткові налаштування Зробити турнір приватним та обмежити доступ паролем Приєднатись Відступити @@ -573,8 +572,7 @@ Якщо немає, то залиште порожнім Профіль Редагувати профіль - Ім\'я - Прізвище + Справжнє ім\'я Оберіть свій символ Символ Це налаштування вимикає символи всіх користувачів сайту. @@ -623,9 +621,7 @@ Причина Що трапилося? Нечесна гра - Образа Тролінг - Маніпуляції з рейтингом Інше Вставте посилання на гру (ігри) та поясніть, що не так із поведінкою цього користувача. Не пишіть просто \"він шахраює\", а розкажіть, як ви дійшли до такого висновку. Вашу скаргу розглянуть швидше, якщо ви напишете її англійською. Будь ласка, додайте посилання на хоча б одну нечесну гру. @@ -660,6 +656,7 @@ Повільна Усередині шахівниці Поза шахівницею + Усі поля дошки У повільних іграх Завжди Ніколи diff --git a/translation/dest/site/ur-PK.xml b/translation/dest/site/ur-PK.xml index 526d2eccbe05f..bde29359525f3 100644 --- a/translation/dest/site/ur-PK.xml +++ b/translation/dest/site/ur-PK.xml @@ -398,9 +398,6 @@ ٹورنامنٹ کا ایک محتاط نام رکھیں. کسی قسم کی نا مناسب حرکت کی وجہ سے آپ کا اکاونٹ بند ہو سکتا ہے۔. خانے کو خالی چھوڑنے سے ٹورنامنٹ کا نام کسی مشہور کھلاڑی پر رکھ دیا جائے گا. - ہمارہ مشورہ ہے کہ اس کے ساتھ چھیڑ چھاڑ نہ کریں. - اگر آپ داخلے کی شرائط مقرر کریں گے تو ٹورنامنٹ میں کم کھلاڑی آئیں گے. - اعلی ترتیبات دکھائیں ٹورنامنٹ کی حیثیت ذاتی کر دیں، اور رسائی صرف پاس ورڈ کے ذریعے ہو سکے گی شامل ہوں دستبردار ہوں @@ -437,8 +434,6 @@ اگر کوئی نہیں تو خالی چھوڑ دیں اکاؤنٹ اکاؤنٹ میں ترمیم کریں - پہلا نام - آخری نام سوانح حیات آپ کا شکریہ! سوشل میڈیا کے لنکس @@ -476,7 +471,6 @@ شکایت کی وجہ معاملہ؟ بدعنوانی - ہتک آمیز گفتگو سیاپہ فروشی دیگر متاثرہ مقابلوں کے رابطے یہاں داخل کریں اور شکایت کی وجوہات بیان کریں diff --git a/translation/dest/site/uz-UZ.xml b/translation/dest/site/uz-UZ.xml index 548d68b4af6cd..b275b42e4b21c 100644 --- a/translation/dest/site/uz-UZ.xml +++ b/translation/dest/site/uz-UZ.xml @@ -5,6 +5,7 @@ Kimnidir o\'yinga taklif qilish uchun ushbu URL ni olib unga bering O‘yin tugadi Sherik tanlanishi kutilayabdi + Yoki ushbu QR kodni skanerlash uchun opponentingizga bering Kutib turing Sizni yurishingiz %1$s daraja %2$s @@ -69,7 +70,10 @@ O\'zgarishlarni qo\'llash Asosiy chiziqni yaratish Bu yerdan o\'chirish + Variantlarni yashirish + Variantlarni koʻrsatish Majburiy o\'zgartirish + Variantning PGN ini nusxalash Yurish Yo\'qotish varianti Yutuq varianti @@ -115,6 +119,7 @@ Ochiq ta\'lim Yoqish Yaxshi yo\'l ko\'rsatmasi + Variantlar strelkalarini koʻrsatish Baholash ko\'rsatkichi Bir nechta chiziqlar Markaziy protsessorlar soni @@ -273,6 +278,7 @@ O\'yinni bekor qilish O\'yin bekor qilindi Standart + Foydalanuvchi joyidan Chegaralanmagan Holat Norasmiy @@ -402,6 +408,7 @@ O\'yinni import qilish PGN o\'yinni qo\'ying va qayta o\'ynashlar, kompyuter tahlili, o\'yin chati va ulashish linkiga ega bo\'ling. Variantlar o‘chiriladi. Ularni saqlash uchun PGN yordamida \"ta‘lim\"da saqlang. + Ushbu PGN ommaga ochiq boʻladi. Oʻyinni yopiq holatda import qilish uchun study - oʻrganish boʻlimidan foydalaning. %s import qilingan o\'yin %s import qilingan o\'yinlar @@ -414,6 +421,7 @@ Qoralar bitta yurishda mot Qayta urinish Qaytadan ulanmoqda + Oflayn %s ta o\'rtoq onlayn %s ta o\'rtoqlar onlayn @@ -459,9 +467,6 @@ Turnir uchun juda ham xavfsiz nom tanlash. Sal noqulayku lekin so\'rashimiz joiz, balkim sizni akkountingizni yopish kerak. Turnir nomini tasodifiy Grassmeyster nomiga moslab qo\'yish uchun ochiq qoldiring. - Biz sizga bu joyda teginmaslikni maslahat beramiz. - Agar siz kirish shartlarini sozlagan bo\'lsangiz, sizni turniringiz kam kishidan iborat bo\'ladi. - Qo\'shimcha shartlarni namoyish qilish Yopiq turnir yarating va ishtirokchilarga parol yordamida kirishga huquq bering Qo\'shilish Chekinish @@ -482,6 +487,7 @@ Yurilgan yo\'llar Oqlar g\'alabalari Qoralar g\'alabalari + Oʻyin tezligi Durranglar Keyingi %s turnir: O\'rtacha raqiblar soni @@ -500,8 +506,9 @@ Agar yo\'q bo\'lsa bo\'sh qoldiring Profil Profilni o\'zgartirish - Berilgan nom - Familiya + Haqiqiy nomi + Oʻz kulgichingizni belgilang + Kulgich Biografiya Rahmat! Ijtimoiy media linklari @@ -543,9 +550,7 @@ Sabab Nima gap? Firibgarlik - Haqorat Yo\'q joydan janjal chiqarish - Reyting nayrangliklari Boshqa O\'yin(lar)ga murojaatni bering va ushbu o\'yinchini tutishini nimasi yaxshi emas izohlang. Faqatgina \"u aldayabdi\" deb qo\'ya qolmasdan, balkim nima uchun bunday xulosaga kelganingizni ingliz tilida tushuntirsangiz uni ko\'rib chiqishimiz tezlashadi. Iltimos kamida bitta o\'yinga murojatni keltiring. diff --git a/translation/dest/site/vi-VN.xml b/translation/dest/site/vi-VN.xml index d74461afe3a23..43046700c5cca 100644 --- a/translation/dest/site/vi-VN.xml +++ b/translation/dest/site/vi-VN.xml @@ -31,7 +31,7 @@ Vua ở trung tâm Ba lần chiếu Cuộc đua kết thúc - Hết cờ theo luật + Hết cờ theo luật biến thể Đối thủ mới Đối thủ muốn chơi một ván cờ mới với bạn Tham gia ván cờ @@ -53,7 +53,7 @@ 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 + Có sẵn máy tính phân tích Phân tích máy tính bị vô hiệu hóa Bàn cờ phân tích Độ sâu %s @@ -65,17 +65,17 @@ Phân tích sâu hơn Hiện các mối đe dọa trong trình duyệt cục bộ - Kích hoạt đánh giá địa phương - Thay đổi biến - Biến chính + Bật/Tắt đánh giá cục bộ + Về biến chính + Trở thành biến chính Xoá từ đây - Thu gọn các biến - Mở rộng các biến + Thu gọn các biến + Mở rộng các biến Đổi biến Sao chép biến PGN Nước cờ Nước đi dẫn đến hết cờ - Nước chiếu hết theo luật + Thắng theo luật biến thể Thiếu quân để chiếu hết Tiến tốt Ăn quân @@ -176,7 +176,7 @@ Hôm qua Số phút cho mỗi bên Biến thể - Biến chơi + Biến thể Kiểu thời gian Thời gian thực Cờ qua thư @@ -193,7 +193,7 @@ Thời gian Hệ số - Thống kê hệ số + Các thống kê hệ số Tên đăng nhập Tên đăng nhập hoặc email Thay đổi tên đăng nhập @@ -435,9 +435,6 @@ trò chuyện trong ván đấu và có một URL có thể chia sẻ công khai Hãy chọn tên chuẩn mực cho giải đấu. Một hành động dù chỉ một chút không thích hợp, tài khoản của bạn có thể bị khoá. Hãy để trống để lấy tên theo tên một kỳ thủ cờ vua nổi tiếng. - Chúng tôi khuyên bạn không nên thay đổi. - Nếu bạn thiết lập điều kiện tham gia, giải của bạn sẽ có ít người chơi hơn. - Hiện các thiết lập nâng cao Đặt giải đấu ở chế độ riêng tư và giới hạn tham gia bởi mật khẩu Tham gia Rút lui @@ -447,7 +444,7 @@ trò chuyện trong ván đấu và có một URL có thể chia sẻ công khai Tạo bởi Giải đấu đang diễn ra Đã đóng việc sắp xếp cặp đấu. - Cặp đấu đang được xếp, %s hãy sẵn sàng! + %s chờ nhé, đang xếp cặp đấu, chuẩn bị sẵn sàng! Tạm rút Tiếp tục Bạn đã vào ván! @@ -477,8 +474,7 @@ trò chuyện trong ván đấu và có một URL có thể chia sẻ công khai Nếu không có, hãy để trống Hồ sơ Chỉnh sửa thông tin cá nhân - Tên - Họ + Tên thật Đặt biểu tượng của bạn Biểu tượng Có một cài đặt để ẩn tất cả biểu tượng của người dùng trên toàn bộ trang web. @@ -524,9 +520,7 @@ trò chuyện trong ván đấu và có một URL có thể chia sẻ công khai Lý do Có chuyện gì vậy? Gian lận - Xúc phạm Chọc tức, chơi khăm - Thao túng xếp hạng Khác Dán đường dẫn đến (các) ván cờ và giải thích về vấn đề của kỳ thủ này. Đừng chỉ nói \"họ gian lận\" mà hãy miêu tả chi tiết nhất có thể. Vấn đề sẽ được giải quyết nhanh hơn nếu bạn viết bằng tiếng Anh. Hãy cung cấp ít nhất một đường dẫn đến ván cờ bị gian lận. @@ -561,6 +555,7 @@ trò chuyện trong ván đấu và có một URL có thể chia sẻ công khai Chậm Bên trong bàn cờ Bên ngoài bàn cờ + Tất cả ô vuông của bàn cờ Chỉ khi chơi cờ chậm Luôn luôn Không bao giờ diff --git a/translation/dest/site/zh-CN.xml b/translation/dest/site/zh-CN.xml index e899a5ac84c21..4bb20eed8e11c 100644 --- a/translation/dest/site/zh-CN.xml +++ b/translation/dest/site/zh-CN.xml @@ -69,8 +69,8 @@ 提升变着 做为主线 从此处开始删除 - 折叠变着 - 展开变着 + 折叠变着 + 展开变着 强制作为变着 复制变着的PGN 着法 @@ -434,9 +434,6 @@ 为锦标赛提出一个无争议的名称。 即使只有一点点违规的内容都可能导致你的账户被封禁。 若留空,将会随机选择一位著名的大师的名字作为锦标赛名称。 - 我们建议你不更改这些设置。 - 如果你设置参赛条件,锦标赛的玩家将会更少。 - 显示高级设置 设置比赛私有,并用密码限制访问 参与锦标赛 退出锦标赛 @@ -476,8 +473,7 @@ 如果没有,请留空 个人资料 编辑个人资料 - - + 真名 设置你的图标 头像 有一个设置可以隐藏整个站点上的所有用户图标。 @@ -523,9 +519,7 @@ 原因 举报原因? 作弊 - 侮辱 捣乱 - 操纵等级分 其他 请附上棋局链接解释该用户的行为问题。例如如果你怀疑某用户作弊,请不要只说 “对手作弊”。请解释为什么你认为对手作弊。如果你用英语举报,我们将会更快作出答复。 请提供至少一局作弊的棋局的链接。 diff --git a/translation/dest/site/zh-TW.xml b/translation/dest/site/zh-TW.xml index 00c20a428bac9..80477679c7840 100644 --- a/translation/dest/site/zh-TW.xml +++ b/translation/dest/site/zh-TW.xml @@ -428,9 +428,6 @@ 幫錦標賽挑選一個適合的名字 即便只是一點點的違規都有可能導致您的帳號被封鎖。 若不填入錦標賽的名稱,將會用一位著名的棋手名字來做為錦標賽名稱。 - 我們建議您不要調整這些數據 - 如果你設定入場限制,你的錦標賽選手會比較少。 - 顯示進階設定 把錦標賽設定為私人,並設定密碼來限制進入。 加入 離開 @@ -470,8 +467,6 @@ 如果沒有,請留空 資料 編輯資料 - - 設置你的圖標 圖標 有一個設置可以隱藏整個網站上所有用户圖標。 @@ -516,9 +511,7 @@ 原因 举报原因? 作弊 - 侮辱 钓鱼 - 操縱積分 其他 附上游戏的网址解释该用户的行为问题 請提供至少一局作弊棋局的連結。 diff --git a/translation/dest/streamer/fa-IR.xml b/translation/dest/streamer/fa-IR.xml index f23040f050ec2..22e1f28a71152 100644 --- a/translation/dest/streamer/fa-IR.xml +++ b/translation/dest/streamer/fa-IR.xml @@ -21,7 +21,7 @@ %s ما را بخوانید تا در مدت استریم شما از بازی جوانمردانه برای همه اطمینان حاصل شود. پرسشها و پاسخهای متداول درباره استریم نمودن به صورت منصفانه مزایای جریان با کلمه کلیدی - یک نقشک بَرخَط-محتواساز شعله‌ور در نمایه Lichessتان دریافت کنید. + یک نقشک بَرخَط-محتواساز شعله‌ور در رُخ‌نمای Lichessتان دریافت کنید. در بالای لیست پخش کننده ها پرش کنید. به فالوور های خود در لیچس اطلاع دهید جریان خود را در بازی ها مسابقات و مطالعات خود نشان دهید diff --git a/translation/dest/streamer/fi-FI.xml b/translation/dest/streamer/fi-FI.xml index 455d4c34902f2..84b1a47ad806a 100644 --- a/translation/dest/streamer/fi-FI.xml +++ b/translation/dest/streamer/fi-FI.xml @@ -2,9 +2,9 @@ Lichess-striimaajat Lichess-striimaaja - JUURI NYT! + MENEILLÄÄN! POISSA - Parhaillaan striimaavat: %s + Striimaa parhaillaan: %s Viimeisin striimi %s Ryhdy Lichess-striimaajaksi Onko sinulla Twitch- tai YouTube-kanava? diff --git a/translation/dest/streamer/he-IL.xml b/translation/dest/streamer/he-IL.xml index ab2c79156782c..36e7759187bfd 100644 --- a/translation/dest/streamer/he-IL.xml +++ b/translation/dest/streamer/he-IL.xml @@ -1,12 +1,12 @@ - שדרני ליצ׳ס - שדרן ליצ׳ס + שדרני Lichess + שדרן Lichess בשידור חי! מנותק משדרים כרגע: %s משדר אחרון %s - הפכו לשדרני ליצ׳ס + הפכו לשדרני Lichess יש לך ערוץ יוטיוב או טוויץ\'? יוצאים לדרך! כל השדרנים @@ -15,26 +15,26 @@ הורדת ערכת משדר %s משדר/ת חוקי שידור - יש לכלול את מילת המפתח \"lichess.org\" בכותרת המשדר שלכם ולהשתמש בקטגוריית \"Chess\", כשאתם משדרים בליצ׳ס. - יש להסיר את מילת המפתח כשאתם משדרים תוכן שאינו קשור לליצ׳ס. - ליצ׳ס יזהה את המשדר שלכם בצורה אוטומטית ויפעיל את ההטבות הבאות: + יש לכלול את מילת המפתח \"lichess.org\" בכותרת המשדר שלכם ולהשתמש בקטגוריית \"Chess\", כשאתם משדרים ב־Lichess. + יש להסיר את מילת המפתח כשאתם משדרים תוכן שאינו קשור ל־Lichess. + Lichess יזהה את המשדר שלכם בצורה אוטומטית ויפעיל את ההטבות הבאות: קראו את %s כדי לאפשר משחק הוגן לכולם בזמן המשדר שלכם. המדריך שלנו לשידור הוגן הטבות שידור עם מילת המפתח - תקבלו אייקון משדר בוער בפרופיל הליצ׳ס שלכם. + תקבלו אייקון משדר בוער בפרופיל ה־Lichessשלכם. תועלו למעלה ברשימת המשדרים. - העוקבים שלכם בליצ׳ס יקבלו הודעה. + העוקבים שלכם ב־Lichessיקבלו הודעה. תוכלו להציג את השידור שלכם במשחקים, טורנירים ובלוחות הלמידה שלכם. המשדר שלכם אושר. המשדר שלכם עובר בדיקת מנהלים. אנא מלאו את פרטי המשדר שלכם והעלו תמונה. - כאשר אתם מוכנים להיכנס לרשימת שדרני ליצ׳ס, %s + כאשר אתם מוכנים להיכנס לרשימת שדרני Lichess, %s בקשו בדיקת מנהלים - עמוד השדרנים של ליצ׳ס מכוון את הקהל שלך לפי השפה שבימת השידור שלך סיפקה לנו. הגדירו את השפה הנכונה באפליקציה או בשירות המשמשים אתכם לשידור. + עמוד השדרנים של Lichess מכוון את הקהל שלך לפי השפה שבימת השידור שלך סיפקה לנו. הגדירו את השפה הנכונה באפליקציה או בשירות המשמשים אתכם לשידור. שם המשתמש או כתובת הTwitch שלכם אופציונאלי. אם אין, השאירו את השדה ריק - המזהה (ID) של ערוץ ה-YouTube שלך - השם שלכם כשדרנים בליצ׳ס + המזהה (ID) של ערוץ ה־YouTube שלך + השם שלכם כשדרנים ב־Lichess עד תו %s עד %s תווים diff --git a/translation/dest/study/fa-IR.xml b/translation/dest/study/fa-IR.xml index 6ea7f494e9887..e9b1372cd043f 100644 --- a/translation/dest/study/fa-IR.xml +++ b/translation/dest/study/fa-IR.xml @@ -58,7 +58,7 @@ پیشین بعدی آخرین - اشتراک & صدور + همرسانی و برون‏بُرد نمونه سازی PGN درس بارگیری تمام بازی ها @@ -70,7 +70,7 @@ برای جاسازی این نوشته، این کد را در تالار گفت و گو قرار دهید در موقعیت آغازین شروع نمایید شروع از %s - در وبسایت یا وبلاگ خود قرار دهید + در وبگاهتان قرار دهید درباره قرار دادن (در سایت) بیشتر بخوانید فقط مطالعاتِ عمومی می‌توانند جایگذاری شوند! بگشایید diff --git a/translation/dest/study/he-IL.xml b/translation/dest/study/he-IL.xml index 345bff3128ebc..f5f3566f3dbd7 100644 --- a/translation/dest/study/he-IL.xml +++ b/translation/dest/study/he-IL.xml @@ -69,7 +69,7 @@ הPGN של לוח הלמידה הורדת כל המשחקים הPGN של הפרק - העתקת ה-PGN + העתקת ה־PGN הורדת המשחק כתובת לוח הלמידה כתובת האינטרנט של הפרק הנוכחי diff --git a/translation/dest/study/vi-VN.xml b/translation/dest/study/vi-VN.xml index 50240b54fbbe5..62dadeefea094 100644 --- a/translation/dest/study/vi-VN.xml +++ b/translation/dest/study/vi-VN.xml @@ -133,7 +133,7 @@ Nước đi thiên tài Sai lầm nghiêm trọng Nước đi hay - Nước cần suy tính thêm + Nước đi mơ hồ Nước duy nhất Zugzwang Thế trận cân bằng diff --git a/translation/dest/swiss/fa-IR.xml b/translation/dest/swiss/fa-IR.xml index 713cdf9f5474a..87df9c4f5efcd 100644 --- a/translation/dest/swiss/fa-IR.xml +++ b/translation/dest/swiss/fa-IR.xml @@ -39,6 +39,15 @@ فاصله بین دورها رویارویی‌های ممنوع نام کاربری بازیکنانی که نباید با هم بازی کنند (مثلا خواهر و برادرها). دو نام کاربری در هر خط، با فاصله از هم جدا شوند. + رویارویی دستی در دور پسین + همه رویارویی‌های دور پسین را دستی مشخص کنید. یک جفت بازیکن در هر خط. نمونه: +بازیکن‌آ بازیکن‌ب +بازیکن‌پ بازیکن‌ت +برای استراحت دادن (یک امتیاز) به یک بازیکن به جای رویارویی، یک خط مانند این بیفزایید: +بازیکن‌ث ۱ +بازیکنان نبوده، غایب در نظر گرفته می‌شوند و صفر امتیاز می‌گیرند. +اگر این خانه را خالی گذارید، Lichess خودکار رویارویی‌ها را می‌چیند. + باید آخرین بازی سوییسی‌شان را کرده باشند مسابقات سوئیسی جدید چه زمانی از مسابقات با ساختار سوئیسی به جای آرنا استفاده کنیم؟ در مسابقه با فرم سوئیسی، تمام شرکت کننده ها به تعداد برابر بازی انجام می دهند و هر دو بازیکن فقط یک بار با یکدیگر بازی می کنند. diff --git a/translation/dest/swiss/he-IL.xml b/translation/dest/swiss/he-IL.xml index 081f92e91e867..ea008dce94db9 100644 --- a/translation/dest/swiss/he-IL.xml +++ b/translation/dest/swiss/he-IL.xml @@ -89,7 +89,7 @@ PlayerE 1 מה קורה אם שחקן לא משחק? השעון שלו יתקתק, ״יפול לו הדגל״ והוא יפסיד את המשחק. לאחר מכן הוא יוצא מהטורניר כדי לא להפסיד משחקנים נוספים. הוא יוכל להצטרף מחדש בכל שלב. מה נעשה באשר לשחקים שלא מופיעים למשחק? - שחקנים שנרשמים לטורנירים שוויצריים אבל לא משחקים כנדרש יכולים להוות בעיה. כדי למנוע את זה, ליצ׳ס מונע משחקים שלא מופיעים להצטרף לטורנירים שוויצריים למשך זמן מסויים. היוצר של הטורניר יכול לבחור לאשר את הצטרפותם בכל זאת. + שחקנים שנרשמים לטורנירים שוויצריים אבל לא משחקים כנדרש יכולים להוות בעיה. כדי למנוע את זה, Lichess מונע משחקים שלא מופיעים להצטרף לטורנירים שוויצריים למשך זמן מסויים. היוצר של הטורניר יכול לבחור לאשר את הצטרפותם בכל זאת. האם ניתן להצטרף באיחור? כן, עד אשר שוחקו יותר ממחצית הסבבים. לדוגמה, בטורניר של 11 סבבים ניתן להצטרף לפני שסיבוב 6 מתחיל ואם יש 12 סבבים ניתן להצטרף לפני שסיבוב 7 מתחיל. מצטרפים באיחור מקבלים חצי נקודה בלבד, אף אם החמיצו מספר סבבים. האם טורנירים שוויצריים יחליפו את טורנירי הזירה? diff --git a/translation/dest/swiss/vi-VN.xml b/translation/dest/swiss/vi-VN.xml index 1db024a122804..6719e4bdf2d5a 100644 --- a/translation/dest/swiss/vi-VN.xml +++ b/translation/dest/swiss/vi-VN.xml @@ -24,7 +24,7 @@ Bắt đầu trong Vòng đấu tiếp theo - Ván cờ hiện đang chơi + Ván đấu đang diễn ra Thời gian bắt đầu giải đấu Số vòng đấu @@ -117,7 +117,7 @@ Thứ gần nhất bạn có thể có với một giải đấu vòng tròn là Chỉ cho phép những người dùng được chỉ định trước tham gia Ngoại trừ những người trong danh sách này, những người khác đều bị cấm tham gia. Mỗi dòng một tên người dùng. Chơi các ván đấu của bạn - Miễn + Được miễn Vắng mặt Điểm số phụ diff --git a/translation/dest/team/fa-IR.xml b/translation/dest/team/fa-IR.xml index faf93c170d6c4..fb5bf2b3f6578 100644 --- a/translation/dest/team/fa-IR.xml +++ b/translation/dest/team/fa-IR.xml @@ -55,6 +55,7 @@ درخواست‌های رد شده برای دسترسی به اخبار و رویداد ها در تیم رسمی %s عضو شوید صفحه تیم ها + این مسابقات به پایان رسیده‌است و تیم‌ها دیگر نمی‌توانند به‌روز شوند. تیم‌هایی که در این نبرد با یکدیگر رقابت خواهند کرد را لیست کنید. هر تیم در یک خط. از تکمیل خودکار استفاده کنید. می‌توانید این لیست را از یک مسابقه به مسابقات دیگر کپی کنید! diff --git a/translation/dest/tfa/he-IL.xml b/translation/dest/tfa/he-IL.xml index c7f03260caac6..6772f38fdf9dc 100644 --- a/translation/dest/tfa/he-IL.xml +++ b/translation/dest/tfa/he-IL.xml @@ -1,7 +1,7 @@ אימות דו־שלבי - אימות דו-שלבי מוסיף עוד שכבה של אבטחה לחשבון שלך. + אימות דו־שלבי מוסיף עוד שכבה של אבטחה לחשבון שלך. התקינו אפליקציה לאימות דו־שלבי. אנו ממליצים על היישומים הבאים: סרקו את הברקוד עם האפליקציה. הכניסו את הסיסמה ואת קוד האימות שנוצר על ידי האפליקציה כדי להשלים את הרישום. תזדקק/י לקוד אימות בכל התחברות מחדש. @@ -10,10 +10,10 @@ הערה: אם איבדתם את הגישה לקודים המשמשים אתכם לאימות דו־שלבי, השתמשו ב%s באמצעות אימייל. הפעלת אימות דו־שלבי השבתת אימות דו־שלבי - אימות דו-שלבי הופעל + אימות דו־שלבי הופעל יש צורך בסיסמה ובקוד אימות מהאפליקציה שאתם משתמשים בה כדי לבטל אימות דו־שלבי. פתחו את אפליקציית האימות הדו־שלבי שברשותכם להצגת קוד האימות ואמתו את זהותכם. - אנא הפעל/י אימות דו־שלבי כדי לאבטח את חשבונך ב-https://lichess.org/account/twofactor. + אנא הפעל/י אימות דו־שלבי כדי לאבטח את חשבונך ב־https://lichess.org/account/twofactor. קיבלת את ההודעה הזאת כיוון שיש לך אחריות מיוחדת כמו מוביל/ה של קבוצה, מאמן/ת, מורה או שדרן/ית diff --git a/translation/dest/timeago/de-DE.xml b/translation/dest/timeago/de-DE.xml index 3f4be9ab072bd..f0004f2e69bf6 100644 --- a/translation/dest/timeago/de-DE.xml +++ b/translation/dest/timeago/de-DE.xml @@ -59,8 +59,8 @@ %s Minuten verbleibend - %s Stunden verbleiben - %s Stunden übrig + %s Stunde verbleiben + %s Stunden verbleiben abgeschlossen diff --git a/translation/dest/timeago/uz-UZ.xml b/translation/dest/timeago/uz-UZ.xml index 5f328e6bef923..47b37e4303212 100644 --- a/translation/dest/timeago/uz-UZ.xml +++ b/translation/dest/timeago/uz-UZ.xml @@ -54,4 +54,13 @@ %s yil avval %s yil avval + + %s daqiqa qoldi + %s daqiqa qoldi + + + %s soat qoldi + %s soat qoldi + + tugallangan diff --git a/translation/dest/ublog/af-ZA.xml b/translation/dest/ublog/af-ZA.xml index 25e1ac466b595..1ae52f5f860f1 100644 --- a/translation/dest/ublog/af-ZA.xml +++ b/translation/dest/ublog/af-ZA.xml @@ -48,5 +48,5 @@ Plaas slegs veilige en bedagsame inhoud. Moenie iemand anders se inhoud kopieer nie. Enigiets onvanpas kan lei tot die sluiting van jou rekening. Ons eenvoudige wenke vir goeie plasings - Jy word deur die blog-outeur geblokkeer. + Jy word deur die blog-outeur geblokkeer. diff --git a/translation/dest/ublog/bg-BG.xml b/translation/dest/ublog/bg-BG.xml index 877cec25adc3b..a92941f0d03a5 100644 --- a/translation/dest/ublog/bg-BG.xml +++ b/translation/dest/ublog/bg-BG.xml @@ -43,5 +43,5 @@ Моля, публикувайте само безопасно и уважително съдържание. Не копирайте чуждо съдържание. За всяко неподходящо действие акаунтът ви може да бъде закрит. Нашите прости съвети за писане на страхотни публикации в блогове - Блокирани сте от автора на блога. + Блокирани сте от автора на блога. diff --git a/translation/dest/ublog/ca-ES.xml b/translation/dest/ublog/ca-ES.xml index 4606dbe115c5f..bc1cee1dd17b0 100644 --- a/translation/dest/ublog/ca-ES.xml +++ b/translation/dest/ublog/ca-ES.xml @@ -52,5 +52,5 @@ Qualsevol contingut inapropiat podria implicar el tancament del teu compte. Els nostres senzills consells per escriure bones entrades al blog Discuteix aquesta publicació del blog en el fòrum - Estàs bloquejat per l\'autor del blog. + Estàs bloquejat per l\'autor del blog. diff --git a/translation/dest/ublog/ckb-IR.xml b/translation/dest/ublog/ckb-IR.xml index e2ab71cf41938..a5f3b4fb45c16 100644 --- a/translation/dest/ublog/ckb-IR.xml +++ b/translation/dest/ublog/ckb-IR.xml @@ -44,5 +44,5 @@ هەرشتێکی نەگوونجاو دەشێت ببێتە هۆی داخستنی هەژمارەکەت. پێشنیارە سادەکانی ئێمە بۆ نوسینی پەیامێکی کەسی بەهێز لە مەکۆدا باسی ئەم پۆستەی بلۆگەکە بکەن - تۆ لەلایەن نووسەری بلۆگەکەوە بلۆک کراویت. + تۆ لەلایەن نووسەری بلۆگەکەوە بلۆک کراویت. diff --git a/translation/dest/ublog/cs-CZ.xml b/translation/dest/ublog/cs-CZ.xml index da5d6d5865464..3b77935941b1c 100644 --- a/translation/dest/ublog/cs-CZ.xml +++ b/translation/dest/ublog/cs-CZ.xml @@ -58,5 +58,5 @@ Cokoliv nevhodného může způsobit smazání vašeho účtu. Naše jednoduché rady, jak psát skvělé příspěvky do blogu Diskutujte o tomto příspěvku blogu ve fóru - Jste zablokováni autorem blogu. + Jste zablokováni autorem blogu. diff --git a/translation/dest/ublog/da-DK.xml b/translation/dest/ublog/da-DK.xml index cc4170b52c02e..096c1eb5e64c1 100644 --- a/translation/dest/ublog/da-DK.xml +++ b/translation/dest/ublog/da-DK.xml @@ -52,5 +52,5 @@ Alt upassende kan medføre, at din konto lukkes. Vores enkle tips til at skrive gode blogindlæg Diskutér dette blogindlæg i forummet - Du er blokeret af blogforfatteren. + Du er blokeret af blogforfatteren. diff --git a/translation/dest/ublog/de-DE.xml b/translation/dest/ublog/de-DE.xml index 710a729caccb6..1b8643ea509c0 100644 --- a/translation/dest/ublog/de-DE.xml +++ b/translation/dest/ublog/de-DE.xml @@ -52,5 +52,5 @@ Jegliche unangemessenen Inhalte können zur Schließung deines Benutzerkontos führen. Unsere einfachen Tipps, um tolle Blog-Beiträge zu schreiben Diesen Blog-Beitrag im Forum diskutieren - Du wurdest vom Blog-Author gesperrt. + Du wurdest vom Blog-Author gesperrt. diff --git a/translation/dest/ublog/en-US.xml b/translation/dest/ublog/en-US.xml index cfec443280d00..092f859872c97 100644 --- a/translation/dest/ublog/en-US.xml +++ b/translation/dest/ublog/en-US.xml @@ -52,5 +52,5 @@ Anything inappropriate could get your account closed. Our simple tips to write great blog posts Discuss this blog post in the forum - You are blocked by the blog author. + You are blocked by the blog author. diff --git a/translation/dest/ublog/es-ES.xml b/translation/dest/ublog/es-ES.xml index 9e0226b5aa54e..db2487577c1a0 100644 --- a/translation/dest/ublog/es-ES.xml +++ b/translation/dest/ublog/es-ES.xml @@ -52,5 +52,5 @@ Cualquier cosa inapropiada puede causar el cierre de tu cuenta. Nuestros sencillos consejos para escribir buenas entradas en el blog Discutir esta publicación en el foro - Estás bloqueado por el autor del blog. + Estás bloqueado por el autor del blog. diff --git a/translation/dest/ublog/eu-ES.xml b/translation/dest/ublog/eu-ES.xml index f2bd9dac25ad9..8b6ac4dd24c42 100644 --- a/translation/dest/ublog/eu-ES.xml +++ b/translation/dest/ublog/eu-ES.xml @@ -52,5 +52,5 @@ Errespetua galduz gero, zure kontua itxiko dugu. Blogeko sarrerak idazteko gure aholkuak Eztabaidatu artikulu honi buruz foroan - Blogaren egileak blokeatu egin zaitu. + Blogaren egileak blokeatu egin zaitu. diff --git a/translation/dest/ublog/fi-FI.xml b/translation/dest/ublog/fi-FI.xml index 9ec1042d94ec6..c0b2caf28d537 100644 --- a/translation/dest/ublog/fi-FI.xml +++ b/translation/dest/ublog/fi-FI.xml @@ -52,5 +52,5 @@ Epäasiallisen sisällön julkaisu voi johtaa käyttäjätunnuksesi sulkemiseen. Yksinkertaiset vinkkimme erinomaisten blogikirjoitusten laatimiseen Keskustele tästä blogikirjoituksesta foorumissa - Blogin tekijä on estänyt sinut. + Blogin tekijä on estänyt sinut. diff --git a/translation/dest/ublog/fr-FR.xml b/translation/dest/ublog/fr-FR.xml index b071c660909ad..a8170b0761f77 100644 --- a/translation/dest/ublog/fr-FR.xml +++ b/translation/dest/ublog/fr-FR.xml @@ -52,5 +52,5 @@ Toute conduite inappropriée peut aboutir à la fermeture de votre compte. Conseils pratiques pour rédiger de bons messages Discuter de cet article de blogue dans le forum - Vous êtes bloqué par l\'auteur du blogue. + Vous êtes bloqué par l\'auteur du blogue. diff --git a/translation/dest/ublog/gl-ES.xml b/translation/dest/ublog/gl-ES.xml index 6d24b1956c1ac..c8b80cdcd2de7 100644 --- a/translation/dest/ublog/gl-ES.xml +++ b/translation/dest/ublog/gl-ES.xml @@ -52,5 +52,5 @@ Calquera cousa inadecuada pode facer que pechemos a túa conta. Os nosos sinxelos consellos para escribir boas publicacións Falar desta publicación no foro - Estás bloqueado polo autor do blog. + Estás bloqueado polo autor do blog. diff --git a/translation/dest/ublog/gsw-CH.xml b/translation/dest/ublog/gsw-CH.xml index 018671d2a3a27..c3e2528b1ac2f 100644 --- a/translation/dest/ublog/gsw-CH.xml +++ b/translation/dest/ublog/gsw-CH.xml @@ -52,5 +52,5 @@ Irgendwelchi unagmässeni Inhält, chönd zur Schlüssig vu dim Konto fühere. Eusi simple Tips, zum tolli Tagebuech-Biträg poschte Diskutier de Tagebuech-Bitrag im Forum - De Autor - vu dem Tagebuech - hät dich blockiert. + De Autor - vu dem Tagebuech - hät dich blockiert. diff --git a/translation/dest/ublog/he-IL.xml b/translation/dest/ublog/he-IL.xml index fbd90e430e0b8..938317aac177a 100644 --- a/translation/dest/ublog/he-IL.xml +++ b/translation/dest/ublog/he-IL.xml @@ -53,10 +53,10 @@ מחיקת התמונה מומלץ להשתמש בתמונות מהאתרים הבאים: בחר/י את הנושאים שהפוסט שלך עוסק בהם - תוכל להשתמש בתמונות שצילמת או יצרת, צילומי מסך מתוך ליצ׳ס... כל מה שלא מוגן בזכויות יוצרים ושייך למישהו אחר. + תוכל להשתמש בתמונות שצילמת או יצרת, צילומי מסך מתוך Lichess... כל מה שלא מוגן בזכויות יוצרים ושייך למישהו אחר. נא לפרסם רק תוכן מכבד ובטוח. אין להעתיק תוכן של מישהו אחר. כל דבר לא הולם עלול להוביל לסגירת חשבונך. הטיפים שלנו לכתיבת פוסטים מעולים שיחה על הבלוג הזה בפורומים - נחסמת על-ידי כותב הבלוג. + נחסמת על־ידי כותב הבלוג. diff --git a/translation/dest/ublog/it-IT.xml b/translation/dest/ublog/it-IT.xml index f6f23330fac43..fa38acdc5eff5 100644 --- a/translation/dest/ublog/it-IT.xml +++ b/translation/dest/ublog/it-IT.xml @@ -52,5 +52,5 @@ Qualsiasi contenuto inappropriato potrebbe portare alla chiusura del tuo account. I nostri suggerimenti per scrivere ottimi messaggi sul blog Discuti di questo post del blog nel forum - Sei stato bloccato dall\'autore del blog. + Sei stato bloccato dall\'autore del blog. diff --git a/translation/dest/ublog/ja-JP.xml b/translation/dest/ublog/ja-JP.xml index 510091069a880..5c9588cd856e3 100644 --- a/translation/dest/ublog/ja-JP.xml +++ b/translation/dest/ublog/ja-JP.xml @@ -49,5 +49,5 @@ 不適切な内容の場合、あなたのアカウントを停止することがあります。 すばらしいブログを書くための簡単なヒント このブログ記事についてフォーラムで話す - ブログの筆者によってブロックされています。 + ブログの筆者によってブロックされています。 diff --git a/translation/dest/ublog/ko-KR.xml b/translation/dest/ublog/ko-KR.xml index a8ac1cc1eb4df..b52b1b0765061 100644 --- a/translation/dest/ublog/ko-KR.xml +++ b/translation/dest/ublog/ko-KR.xml @@ -49,5 +49,5 @@ 부적절한 행위로 인해 계정이 폐쇄될 수 있습니다. 멋진 블로그 게시물을 작성하기 위한 간단한 팁 포럼에서 이 게시글에 대해 토론하기 - 이 블로그 글의 작성자가 당신을 차단했습니다. + 이 블로그 글의 작성자가 당신을 차단했습니다. diff --git a/translation/dest/ublog/lb-LU.xml b/translation/dest/ublog/lb-LU.xml index d784b5419225a..78710b95b58d5 100644 --- a/translation/dest/ublog/lb-LU.xml +++ b/translation/dest/ublog/lb-LU.xml @@ -52,5 +52,5 @@ Alles, wat net ubruecht ass, kann dozou féieren, datt däi Kont zougemaach gëtt. Eis einfach Tuyaue fir flott Blog-Bäiträg ze schreiwen Iwwert dëse Blog-Bäitrag am Forum diskutéieren - Du goufs vum Auteur vum Blog blockéiert. + Du goufs vum Auteur vum Blog blockéiert. diff --git a/translation/dest/ublog/lt-LT.xml b/translation/dest/ublog/lt-LT.xml index e5334ae374787..fa520b1295604 100644 --- a/translation/dest/ublog/lt-LT.xml +++ b/translation/dest/ublog/lt-LT.xml @@ -56,5 +56,5 @@ Bet koks nepriimtinas turinys privers mus uždaryti jūsų paskyrą. Keli patarimai kaip rašyti puikius tinklaraščio įrašus Diskutuoti apie šį įrašą forume - Jūs buvote tinklaraščio autoriaus užblokuotas. + Jūs buvote tinklaraščio autoriaus užblokuotas. diff --git a/translation/dest/ublog/nb-NO.xml b/translation/dest/ublog/nb-NO.xml index 336cda7f20382..dd9ace69ae92b 100644 --- a/translation/dest/ublog/nb-NO.xml +++ b/translation/dest/ublog/nb-NO.xml @@ -52,5 +52,5 @@ Upassende innhold kan føre til at brukerkontoen din blir stengt. Våre enkle tips for å skrive gode blogginnlegg Diskuter dette blogginnlegget i forumet - Bloggforfatteren har blokkert deg. + Bloggforfatteren har blokkert deg. diff --git a/translation/dest/ublog/nl-NL.xml b/translation/dest/ublog/nl-NL.xml index 57389749aa1d0..7eeb892605363 100644 --- a/translation/dest/ublog/nl-NL.xml +++ b/translation/dest/ublog/nl-NL.xml @@ -52,5 +52,5 @@ Alles wat ongepast is kan sluiting van je account tot gevolg hebben. Onze simpele tips om geweldige blogberichten te schrijven Bespreek dit blogbericht in het forum - U bent geblokkeerd door de auteur van de blog. + U bent geblokkeerd door de auteur van de blog. diff --git a/translation/dest/ublog/nn-NO.xml b/translation/dest/ublog/nn-NO.xml index f459cfc6e255d..99b7511bb9660 100644 --- a/translation/dest/ublog/nn-NO.xml +++ b/translation/dest/ublog/nn-NO.xml @@ -52,5 +52,5 @@ Upassande innhald kan føre til at brukarkontoen din blir stengd. Våre enkle tips for å skriva gode blogginnlegg Diskuter dette blogginnlegget i forumet - Bloggforfattaren har blokkert deg. + Bloggforfattaren har blokkert deg. diff --git a/translation/dest/ublog/pl-PL.xml b/translation/dest/ublog/pl-PL.xml index 8b07f334b43c2..54a0e7055ec86 100644 --- a/translation/dest/ublog/pl-PL.xml +++ b/translation/dest/ublog/pl-PL.xml @@ -58,5 +58,5 @@ Każda niestosowna rzecz może spowodować zamknięcie Twojego konta. Nasze proste rady dotyczące pisania dobrych wpisów na blogu Omów ten wpis na forum - Jesteś zablokowany przez autora bloga. + Jesteś zablokowany przez autora bloga. diff --git a/translation/dest/ublog/pt-BR.xml b/translation/dest/ublog/pt-BR.xml index 04c8bdd261bf1..62034c442f326 100644 --- a/translation/dest/ublog/pt-BR.xml +++ b/translation/dest/ublog/pt-BR.xml @@ -52,5 +52,5 @@ Qualquer coisa inapropriada pode levar ao encerramento da sua conta. Nossas dicas simples para escrever ótimas postagens Discutir esta postagem no fórum - Você está bloqueado pelo autor do blog. + Você está bloqueado pelo autor do blog. diff --git a/translation/dest/ublog/pt-PT.xml b/translation/dest/ublog/pt-PT.xml index 381c4f5f6d338..f098c4b406042 100644 --- a/translation/dest/ublog/pt-PT.xml +++ b/translation/dest/ublog/pt-PT.xml @@ -52,5 +52,5 @@ Qualquer coisa inapropriada pode levar ao encerramento da tua conta. As nossas dicas simples para escrever ótimas publicações no blogue Discutir esta publicação no fórum - Estás bloqueado pelo autor do blog. + Estás bloqueado pelo autor do blog. diff --git a/translation/dest/ublog/ro-RO.xml b/translation/dest/ublog/ro-RO.xml index 7010b750e3efd..13696ad07c681 100644 --- a/translation/dest/ublog/ro-RO.xml +++ b/translation/dest/ublog/ro-RO.xml @@ -55,5 +55,5 @@ Orice lucru nepotrivit ar putea duce la închiderea contului tău. Sfaturile noastre simple pentru a scrie cele mai bune postări pe blog Discută această postare pe forum - Ești blocat de autorul blogului. + Ești blocat de autorul blogului. diff --git a/translation/dest/ublog/ru-RU.xml b/translation/dest/ublog/ru-RU.xml index d6b48a5fb1a84..48246cb8a2c42 100644 --- a/translation/dest/ublog/ru-RU.xml +++ b/translation/dest/ublog/ru-RU.xml @@ -58,5 +58,5 @@ Неуместное содержание может стать причиной закрытия вашего аккаунта. Наши простые советы по написанию отличных записей в блоге Обсудить эту запись в блоге на форуме - Вы заблокированы автором блога. + Вы заблокированы автором блога. diff --git a/translation/dest/ublog/sk-SK.xml b/translation/dest/ublog/sk-SK.xml index 073e37e19337e..0623ea4923432 100644 --- a/translation/dest/ublog/sk-SK.xml +++ b/translation/dest/ublog/sk-SK.xml @@ -58,5 +58,5 @@ Akýkoľvek nevhodný obsah môže mať za následok zrušenie Vášho účtu. Naše jednoduché tipy ako písať skvelé blogové príspevky Diskutovať o tomto príspevku na fóre - Autor blogu Vás zablokoval. + Autor blogu Vás zablokoval. diff --git a/translation/dest/ublog/sl-SI.xml b/translation/dest/ublog/sl-SI.xml index f18b27a0e9843..f8e79e63395e3 100644 --- a/translation/dest/ublog/sl-SI.xml +++ b/translation/dest/ublog/sl-SI.xml @@ -48,5 +48,5 @@ Naložite sliko za svojo objavo Razpravljajte o tej objavi v spletnem dnevniku na forumu - Avtor bloga vas je blokiral. + Avtor bloga vas je blokiral. diff --git a/translation/dest/ublog/sq-AL.xml b/translation/dest/ublog/sq-AL.xml index 63a365640b257..93abaaeb3576a 100644 --- a/translation/dest/ublog/sq-AL.xml +++ b/translation/dest/ublog/sq-AL.xml @@ -52,5 +52,5 @@ Çfarëdo gjëje e papërshtatshme mund të sjellë mbylljen e llogarisë tuaj. Ndihmëzat tona të thjeshta për shkrim postimesh të goditura blogu Diskutojeni në forum këtë postim blogu - Jeni bllokuar nga autori i blogut. + Jeni bllokuar nga autori i blogut. diff --git a/translation/dest/ublog/ta-IN.xml b/translation/dest/ublog/ta-IN.xml index 332be3627f954..32ddefd415fc0 100644 --- a/translation/dest/ublog/ta-IN.xml +++ b/translation/dest/ublog/ta-IN.xml @@ -52,5 +52,5 @@ முறையற்ற எதுவும் உங்கள் கணக்கை மூடக்கூடும். சிறந்த வலைப்பதிவு இடுகைகளை எழுத எங்கள் எளிய குறிப்புகள் இந்த வலைப்பதிவு இடுகையை மன்றத்தில் விவாதிக்கவும் - வலைப்பதிவாளரால் நீங்கள் தடைசெய்யப் பட்டுள்ளீர்கள். + வலைப்பதிவாளரால் நீங்கள் தடைசெய்யப் பட்டுள்ளீர்கள். diff --git a/translation/dest/ublog/th-TH.xml b/translation/dest/ublog/th-TH.xml index 1beb15eec991d..025f734cb48f3 100644 --- a/translation/dest/ublog/th-TH.xml +++ b/translation/dest/ublog/th-TH.xml @@ -24,5 +24,5 @@ เครดิตภาพ ลบรูปภาพ หารือเรื่องบล็อกโพสต์ในฟอรั่ม - คุณถูกปิดกั้นโดยผู้เขียนบล็อก + คุณถูกปิดกั้นโดยผู้เขียนบล็อก diff --git a/translation/dest/ublog/uk-UA.xml b/translation/dest/ublog/uk-UA.xml index 965aa04013282..93e8b5ba6ecb4 100644 --- a/translation/dest/ublog/uk-UA.xml +++ b/translation/dest/ublog/uk-UA.xml @@ -58,5 +58,5 @@ Невідповідності в дописі можуть спричинити блокування профілю. Наші прості поради для написання хороших дописів Обговорити цей допис блогу на форумі - Ви заблоковані автором блогу. + Ви заблоковані автором блогу. diff --git a/translation/dest/ublog/vi-VN.xml b/translation/dest/ublog/vi-VN.xml index d51216fd55b28..cbb4e5aa718fd 100644 --- a/translation/dest/ublog/vi-VN.xml +++ b/translation/dest/ublog/vi-VN.xml @@ -49,5 +49,5 @@ Chỉ một hành động không phù hợp, tài khoản của bạn có thể bị đóng. Vài mẹo đơn giản để giúp viết ra một bài blog tuyệt vời Thảo luận về bài blog này trong diễn đàn - Bạn đã bị chặn bởi tác giả blog. + Bạn đã bị chặn bởi tác giả blog. diff --git a/translation/dest/ublog/zh-CN.xml b/translation/dest/ublog/zh-CN.xml index 48f0257bd2f42..296cd30990baf 100644 --- a/translation/dest/ublog/zh-CN.xml +++ b/translation/dest/ublog/zh-CN.xml @@ -49,5 +49,5 @@ 任何违规的内容都可能导致你的账户被封禁。 写出好博文的小提示 在论坛讨论这篇博客 - 您被博客作者拉黑了 + 您被博客作者拉黑了 diff --git a/translation/dest/voiceCommands/bg-BG.xml b/translation/dest/voiceCommands/bg-BG.xml index 3ea04e700dfa8..fc0309cc1ba46 100644 --- a/translation/dest/voiceCommands/bg-BG.xml +++ b/translation/dest/voiceCommands/bg-BG.xml @@ -1,2 +1,6 @@ - + + Гласови команди + Гледайте видео урока + Покажи решението на задачата + diff --git a/translation/dest/voiceCommands/he-IL.xml b/translation/dest/voiceCommands/he-IL.xml index d9c51b6a982b4..629e56917cd01 100644 --- a/translation/dest/voiceCommands/he-IL.xml +++ b/translation/dest/voiceCommands/he-IL.xml @@ -5,11 +5,11 @@ השתמשו בכפתור ״%1$s״ כדי להפעיל את הזיהוי הקולי של המהלכים, בכפתור ״%2$s״ כדי לגשת למסך העזרה הזה ובכפתור ״%3$s כדי לשנות את הגדרות הזיהוי הקולי. אנו מציגים מספר חצים כשאנו לא בטוחים. אמרו את הצבע או המספר של החץ כדי לבחור אותו. אם מופיע סימן מסתובב של מכ״ם ליד החץ, המהלך יבוצע כשהעיגול יושלם. במשך הזמן הזה, תוכלו לומר ״%1$s״ כדי לשחק את המהלך באופן מיידי או ״%2$s״ כדי לבטל. תוכלו גם לומר מספר או סימן של חץ אחר. ניתן לבטל את הטיימר או להתאים אותו לצרכיכם בהגדרות. - הפעילו %s בסביבה רועשת. לחצו באופן מתמשך על כפתור ה-Shift כשאתם משתמשים בזיהוי הקולי במצב זה. + הפעילו %s בסביבה רועשת. לחצו באופן מתמשך על כפתור ה־Shift כשאתם משתמשים בזיהוי הקולי במצב זה. השתמשו באלפבית הפונטי כדי לשפר את הזיהוי של טורים בלוח. ה%s מסבירות את הגדרות הזיהוי הקולי באופן מפורט. הפוסט הזה בבלוג - זוזו ל-e4 או בחרו כלי שנמצא ב-e4 + זוזו ל־e4 או בחרו כלי שנמצא ב־e4 בחרו רץ או הכו אותו הכו את הצריח עם המלכה בצעו הצרחה (לאחד הצדדים) diff --git a/translation/source/broadcast.xml b/translation/source/broadcast.xml index daa6188910282..78a963fd0f486 100644 --- a/translation/source/broadcast.xml +++ b/translation/source/broadcast.xml @@ -16,7 +16,7 @@ Ongoing Upcoming Completed - Lichess detects round completion based on the source games. Use this toggle if there is no source. + Lichess detects round completion, but can get it wrong. Use this to set it manually. Round name Round number Tournament name @@ -29,8 +29,6 @@ Start date in your own timezone Optional, if you know when the event starts Credit the source - Broadcast URL - Current round URL Current game URL Download all rounds Reset this round @@ -45,4 +43,13 @@ Optional: replace player names, ratings and titles Period in seconds Optional, how long to wait between requests. Min 2s, max 60s. Defaults to automatic based on the number of viewers. + FIDE federations + Top 10 rating + FIDE players + FIDE player not found + FIDE profile + Federation + Age this year + Unrated + Recent tournaments diff --git a/translation/source/contact.xml b/translation/source/contact.xml index 40c918c238163..58165b14468f5 100644 --- a/translation/source/contact.xml +++ b/translation/source/contact.xml @@ -55,9 +55,7 @@ In certain circumstances when playing against a bot account, a rated game may not award points if it is determined that the player is abusing the bot for rating points. Error page If you faced an error page, you may report it: - I want to broadcast a tournament Learn how to make your own broadcasts on Lichess - You can also contact the broadcast team about official broadcasts. Appeal for a ban or IP restriction Engine or cheat mark You may send an appeal to %s. diff --git a/translation/source/site.xml b/translation/source/site.xml index d3bfc03e4e9e6..17beb5a640b3a 100644 --- a/translation/source/site.xml +++ b/translation/source/site.xml @@ -467,9 +467,6 @@ Pick a very safe name for the tournament. Anything even slightly inappropriate could get your account closed. Leave empty to name the tournament after a notable chess player. - We recommend not touching these. - If you set entry requirements, your tournament will have fewer players. - Show advanced settings Make the tournament private, and restrict access with a password Join Withdraw @@ -509,8 +506,7 @@ If none, leave empty Profile Edit profile - First name - Surname + Real name Set your flair Flair There is a setting to hide all user flairs across the entire site. @@ -557,9 +553,7 @@ Reason What's the matter? Cheat - Insult Troll - Rating manipulation Other Paste the link to the game(s) and explain what is wrong about this user's behaviour. Don't just say "they cheat", but tell us how you came to this conclusion. Your report will be processed faster if written in English. Please provide at least one link to a cheated game. @@ -594,6 +588,7 @@ Slow Inside the board Outside the board + All squares of the board On slow games Always Never @@ -939,8 +934,7 @@ Cancel the tournament Tournament description Anything special you want to tell the participants? Try to keep it short. Markdown links are available: [name](https://url) - Games are rated -and impact players ratings + Games are rated and impact players ratings Only members of team No restriction Minimum rated games diff --git a/ui/.build/src/build.ts b/ui/.build/src/build.ts index 75373bf48f4a6..23aeeb81ffb0b 100644 --- a/ui/.build/src/build.ts +++ b/ui/.build/src/build.ts @@ -7,7 +7,7 @@ import { sass, stopSass } from './sass'; import { esbuild, stopEsbuild } from './esbuild'; import { copies, stopCopies } from './copies'; import { startMonitor, stopMonitor } from './monitor'; -import { initManifest } from './manifest'; +import { initManifest, writeManifest } from './manifest'; import { clean } from './clean'; import { LichessModule, env, errorMark, colors as c } from './main'; @@ -50,6 +50,7 @@ export async function stop() { } export function postBuild() { + writeManifest(); for (const mod of env.building) { mod.post.forEach((args: string[]) => { env.log(`[${c.grey(mod.name)}] exec - ${c.cyanBold(args.join(' '))}`); diff --git a/ui/.build/src/clean.ts b/ui/.build/src/clean.ts index 4a0e54a2bd788..441188b18b0e5 100644 --- a/ui/.build/src/clean.ts +++ b/ui/.build/src/clean.ts @@ -9,7 +9,7 @@ const globOpts: fg.Options = { markDirectories: true, }; -const globs = [ +const allGlobs = [ '**/node_modules', '**/css/**/gen', 'ui/.build/dist/css', @@ -20,7 +20,7 @@ const globs = [ 'public/css', ]; -export async function clean() { +export async function clean(globs: string[] = allGlobs) { if (!env.clean) return; for (const glob of globs) { diff --git a/ui/.build/src/esbuild.ts b/ui/.build/src/esbuild.ts index b8be7a30a7195..43b74b4c37d26 100644 --- a/ui/.build/src/esbuild.ts +++ b/ui/.build/src/esbuild.ts @@ -58,12 +58,11 @@ export async function esbuild(tsc?: Promise): Promise { const onEndPlugin = { name: 'onEnd', setup(build: es.PluginBuild) { - build.onEnd((result: es.BuildResult) => { + build.onEnd(async (result: es.BuildResult) => { for (const err of result.errors) esbuildMessage(err, true); for (const warn of result.warnings) esbuildMessage(warn); + if (result.errors.length === 0) await jsManifest(result.metafile!); env.done(result.errors.length, 'esbuild'); - if (result.errors.length) return; - jsManifest(result.metafile!); }); }, }; diff --git a/ui/.build/src/manifest.ts b/ui/.build/src/manifest.ts index 074fa5dcd289d..169edc8524df9 100644 --- a/ui/.build/src/manifest.ts +++ b/ui/.build/src/manifest.ts @@ -9,12 +9,12 @@ import { allSources } from './sass'; type Manifest = { [key: string]: { hash?: string; imports?: string[] } }; -const current: { js: Manifest; css: Manifest } = { js: {}, css: {} }; +const current: { js: Manifest; css: Manifest; dirty: boolean } = { js: {}, css: {}, dirty: false }; let writeTimer: NodeJS.Timeout; export async function initManifest() { if (env.building.length === env.modules.size) return; - // we're building a subset of modules. if possible reuse the previously built full manifest to give us + // we're building a subset of modules. reuse the previousl full manifest for // a shot at changes viewable in the browser, otherwise punt. if (!fs.existsSync(env.manifestFile)) return; if (Object.keys(current.js).length && Object.keys(current.css).length) return; @@ -24,15 +24,21 @@ export async function initManifest() { current.css = manifest.css; } +export async function writeManifest() { + if (!current.dirty) return; + clearTimeout(writeTimer); + writeTimer = setTimeout(write, 500); +} + export async function css() { const files = await globArray(path.join(env.cssTempDir, '*.css'), { abs: true }); const css: { name: string; hash: string }[] = await Promise.all(files.map(hashMove)); const newCssManifest: Manifest = {}; for (const { name, hash } of css) newCssManifest[name] = { hash }; - if (enumerableEquivalence(newCssManifest, current.css)) return; + if (isEquivalent(newCssManifest, current.css)) return; current.css = shallowSort({ ...current.css, ...newCssManifest }); - clearTimeout(writeTimer); - writeTimer = setTimeout(write, 500); + current.dirty = true; + writeManifest(); } export async function js(meta: es.Metafile) { @@ -53,10 +59,9 @@ export async function js(meta: es.Metafile) { } newJsManifest[out.name].imports = imports; } - if (enumerableEquivalence(newJsManifest, current.js) && fs.existsSync(env.manifestFile)) return; + if (isEquivalent(newJsManifest, current.js) && fs.existsSync(env.manifestFile)) return; current.js = shallowSort({ ...current.js, ...newJsManifest }); - clearTimeout(writeTimer); - writeTimer = setTimeout(write, 500); + current.dirty = true; } async function write() { @@ -98,6 +103,7 @@ async function write() { JSON.stringify(serverManifest, null, env.prod ? undefined : 2), ), ]); + current.dirty = false; env.log(`Manifest hash ${c.green(hash)}`); } @@ -142,20 +148,16 @@ function parsePath(path: string) { return match ? { name: match[1], hash: match[2] } : undefined; } -function enumerableEquivalence(a: any, b: any): boolean { +function isEquivalent(a: any, b: any): boolean { if (a === b) return true; if (typeof a !== typeof b) return false; if (Array.isArray(a)) - return ( - Array.isArray(b) && - a.length === b.length && - a.every(x => b.find((y: any) => enumerableEquivalence(x, y))) - ); + return Array.isArray(b) && a.length === b.length && a.every(x => b.find((y: any) => isEquivalent(x, y))); if (typeof a !== 'object') return false; const [aKeys, bKeys] = [Object.keys(a), Object.keys(b)]; if (aKeys.length !== bKeys.length) return false; for (const key of aKeys) { - if (!bKeys.includes(key) || !enumerableEquivalence(a[key], b[key])) return false; + if (!bKeys.includes(key) || !isEquivalent(a[key], b[key])) return false; } return true; } diff --git a/ui/.build/src/monitor.ts b/ui/.build/src/monitor.ts index 85d5e31682764..96b7ca5375cc9 100644 --- a/ui/.build/src/monitor.ts +++ b/ui/.build/src/monitor.ts @@ -4,6 +4,7 @@ import * as ps from 'node:process'; import { build, stop } from './build'; import { env } from './main'; import { globArray } from './parse'; +import { clean } from './clean'; import { stopTsc, tsc } from './tsc'; import { stopEsbuild, esbuild } from './esbuild'; @@ -30,7 +31,9 @@ export async function startMonitor(mods: string[]) { stopEsbuild(); clearTimeout(tscTimeout); tscTimeout = setTimeout(() => { - if (!reinitTimeout) esbuild(tsc()); + if (reinitTimeout) return; + clean(['ui/*/tsconfig.tsbuildinfo']); + esbuild(tsc()); }, 2000); }; const packageChange = async () => { diff --git a/ui/@types/lichess/dialog.d.ts b/ui/@types/lichess/dialog.d.ts deleted file mode 100644 index 440e5ee879d5b..0000000000000 --- a/ui/@types/lichess/dialog.d.ts +++ /dev/null @@ -1,60 +0,0 @@ -// implementation: file://./../../site/src/component/dialog.ts - -interface Dialog { - readonly open: boolean; // is visible? - readonly view: HTMLElement; // your content div - readonly returnValue?: 'ok' | 'cancel' | string; // how did we close? - - showModal(): Promise; // resolves on close - show(): Promise; // resolves on close - close(): void; -} - -interface DialogOpts { - class?: string; // zero or more classes for your view div - css?: ({ url: string } | { themed: string })[]; // fetches themed or full url css - htmlText?: string; // content, text will be used as-is - cash?: Cash; // content, overrides htmlText, will be cloned and any 'none' class removed - htmlUrl?: string; // content, overrides htmlText and cash, url will be xhr'd - append?: { node: HTMLElement; selector?: string }[]; // appended to view or selected parents - attrs?: { dialog?: _Snabbdom.Attrs; view?: _Snabbdom.Attrs }; // optional attrs for dialog and view div - action?: Action | Action[]; // if present, add handlers to action buttons - onClose?: (dialog: Dialog) => void; // called when dialog closes - noCloseButton?: boolean; // if true, no upper right corner close button - noClickAway?: boolean; // if true, no click-away-to-close - noScrollable?: boolean; // if true, no scrollable div container. Fixes dialogs containing an auto-completer -} - -interface DomDialogOpts extends DialogOpts { - parent?: Element; // for centering and dom placement, otherwise fixed on document.body - show?: 'modal' | boolean; // if not falsy, auto-show, and if 'modal' remove from dom on close -} - -//snabDialog automatically shows as 'modal' on redraw unless onInsert callback is supplied -interface SnabDialogOpts extends DialogOpts { - vnodes?: _Snabbdom.LooseVNodes; // content, overrides other content properties - onInsert?: (dialog: Dialog) => void; // if supplied, call show() or showModal() manually -} - -// Action can be any "clickable" client button, usually to dismiss the dialog -interface Action { - selector: string; // selector, click handler will be installed - action?: string | ((dialog: Dialog, action: Action) => void); - // if action not provided, just close - // if string, given value will set dialog.returnValue and dialog is closed on click - // if function, it will be called on click and YOU must close the dialog -} - -declare namespace _Snabbdom { - type Attrs = Record; - type Key = string | number | symbol; - type VNode = { - sel: string | undefined; - data: { [key: string]: any } | undefined; - children: Array | undefined; - elm: Node | undefined; - text: string | undefined; - key: Key | undefined; - }; - type LooseVNodes = (VNode | string | undefined | null | boolean)[]; -} diff --git a/ui/@types/lichess/index.d.ts b/ui/@types/lichess/index.d.ts index d9c566a98c71b..100cc892330f4 100644 --- a/ui/@types/lichess/index.d.ts +++ b/ui/@types/lichess/index.d.ts @@ -1,11 +1,8 @@ // eslint-disable-next-line /// - // eslint-disable-next-line /// // eslint-disable-next-line -/// -// eslint-disable-next-line /// // eslint-disable-next-line /// @@ -25,7 +22,6 @@ interface Site { defaultParams: Record; }; mousetrap: LichessMousetrap; // file://./../../site/src/mousetrap.ts - requestIdleCallback(f: () => void, timeout?: number): void; sri: string; storage: LichessStorageHelper; tempStorage: LichessStorageHelper; @@ -38,12 +34,13 @@ interface Site { baseUrl(): string; url(url: string, opts?: AssetUrlOpts): string; flairSrc(flair: Flair): string; - loadCss(path: string): Promise; - loadCssPath(path: string): Promise; - removeCssPath(path: string): void; + loadCss(href: string): Promise; + loadCssPath(key: string): Promise; + removeCss(href: string): void; + removeCssPath(key: string): void; jsModule(name: string): string; loadIife(path: string, opts?: AssetUrlOpts): Promise; - loadEsm(name: string, opts?: EsmModuleOpts): Promise; + loadEsm(key: string, opts?: EsmModuleOpts): Promise; userComplete(opts: UserCompleteOpts): Promise; }; idleTimer(delay: number, onIdle: () => void, onWakeUp: () => void): void; @@ -52,7 +49,6 @@ interface Site { redirect(o: RedirectTo, beep?: boolean): void; reload(): void; watchers(el: HTMLElement): void; - escapeHtml(str: string): string; announce(d: LichessAnnouncement): void; trans(i18n: I18nDict): Trans; sound: SoundI; // file://./../../site/src/sound.ts @@ -71,17 +67,12 @@ interface Site { }; timeago(date: number | Date): string; dateFormat: () => (date: Date) => string; + displayLocale: string; contentLoaded(parent?: HTMLElement): void; blindMode: boolean; makeChat(data: any): any; makeChessground(el: HTMLElement, config: CgConfig): CgApi; log: LichessLog; // file://./../../site/src/log.ts - dialog: { - // file://./../../site/src/dialog.ts - ready: Promise; - dom(opts: DomDialogOpts): Promise; - snab(opts: SnabDialogOpts): _Snabbdom.VNode; - }; // the remaining are not set in site.lichess.globals.ts load: Promise; // DOMContentLoaded promise diff --git a/ui/analyse/css/study/panel/_multiboard.scss b/ui/analyse/css/study/panel/_multiboard.scss index 062c9455d6ad1..f7f4800d144bb 100644 --- a/ui/analyse/css/study/panel/_multiboard.scss +++ b/ui/analyse/css/study/panel/_multiboard.scss @@ -111,7 +111,6 @@ width: 100%; height: 50%; background: $black; - transition: height 1s; } opacity: 0.4; diff --git a/ui/analyse/css/study/relay/_tour.scss b/ui/analyse/css/study/relay/_tour.scss index efaa4b2d0d335..0e42ea7cc37f9 100644 --- a/ui/analyse/css/study/relay/_tour.scss +++ b/ui/analyse/css/study/relay/_tour.scss @@ -1,7 +1,8 @@ $hover-bg: $m-primary_bg--mix-30; .relay-tour { - @extend %box-neat-force; + @extend %box-neat-force, %flex-column; + gap: 3em; background: $c-bg-box; &__side { @extend %flex-column; @@ -256,8 +257,7 @@ $hover-bg: $m-primary_bg--mix-30; } &__nav { - @extend %flex-between; - margin: 2em var(---box-padding); + @extend %flex-between, %box-margin-horiz; gap: 1em; user-select: none; } @@ -286,13 +286,46 @@ $hover-bg: $m-primary_bg--mix-30; } } + &__info { + @extend %box-neat, %flex-between, %box-margin-horiz; + gap: 1em; + background: $c-bg-zebra; + padding: 1em 2em; + > div { + @extend %flex-center-nowrap; + gap: 1em; + } + img { + height: 2em; + } + } + &__markup { + @extend %box-margin-horiz; @include rendered-markdown(2em, 50vh); - margin: 3em var(---box-padding); + } + &__source { + @extend %box-margin-horiz; + font-style: italic; + @media (max-width: at-most($x-large)) { + display: none; + } + &:last-child { + margin-bottom: 3em; + } + } + &__share { + @extend %box-neat, %box-margin-horiz; + background: $c-bg-zebra; + padding: 3em var(---box-padding) 1em var(---box-padding); + h2 { + margin-bottom: 3rem; + } } &__leaderboard { width: auto; + overflow-x: auto; margin: 0 auto 1rem; thead { background: none; @@ -408,6 +441,16 @@ $hover-bg: $m-primary_bg--mix-30; } } +.relay-tour__stats { + .spinner { + margin: 10em auto; + } + canvas { + min-height: 50vh; + margin: 0 2em 0 1em; + } +} + .relay-tour__side { overflow: hidden; &__header { diff --git a/ui/analyse/src/ctrl.ts b/ui/analyse/src/ctrl.ts index 07a7a20b17477..63ae5b59cb025 100644 --- a/ui/analyse/src/ctrl.ts +++ b/ui/analyse/src/ctrl.ts @@ -25,7 +25,7 @@ import { compute as computeAutoShapes } from './autoShape'; import { Config as ChessgroundConfig } from 'chessground/config'; import { CevalCtrl, isEvalBetter, sanIrreversible, EvalMeta } from 'ceval'; import { TreeView } from './treeView/treeView'; -import { defined, prop, Prop, toggle, Toggle } from 'common'; +import { defined, prop, Prop, toggle, Toggle, requestIdleCallback } from 'common'; import { DrawShape } from 'chessground/draw'; import { lichessRules } from 'chessops/compat'; import EvalCache from './evalCache'; @@ -173,7 +173,7 @@ export default class AnalyseCtrl { if (location.hash === '#practice' || (this.study && this.study.data.chapter.practice)) this.togglePractice(); - else if (location.hash === '#menu') site.requestIdleCallback(this.actionMenu.toggle, 500); + else if (location.hash === '#menu') requestIdleCallback(this.actionMenu.toggle, 500); this.startCeval(); keyboard.bind(this); diff --git a/ui/analyse/src/explorer/explorerConfig.ts b/ui/analyse/src/explorer/explorerConfig.ts index 08ac9388327d3..323631d7b1dd2 100644 --- a/ui/analyse/src/explorer/explorerConfig.ts +++ b/ui/analyse/src/explorer/explorerConfig.ts @@ -1,6 +1,7 @@ import { h, VNode } from 'snabbdom'; import { Prop, prop } from 'common'; import * as licon from 'common/licon'; +import { snabDialog } from 'common/dialog'; import { bind, dataIcon, iconTag, onInsert } from 'common/snabbdom'; import { storedProp, storedJsonProp, StoredJsonProp, StoredProp, storedStringProp } from 'common/storage'; import { ExplorerDb, ExplorerSpeed, ExplorerMode } from './interfaces'; @@ -333,7 +334,7 @@ const playerModal = (ctrl: ExplorerConfigCtrl) => { } return '.button-metal'; }; - return site.dialog.snab({ + return snabDialog({ class: 'explorer__config__player__choice', onClose() { ctrl.data.playerName.open(false); diff --git a/ui/analyse/src/ground.ts b/ui/analyse/src/ground.ts index 775555db52c34..55a6756f036d1 100644 --- a/ui/analyse/src/ground.ts +++ b/ui/analyse/src/ground.ts @@ -32,6 +32,7 @@ export function makeConfig(ctrl: AnalyseCtrl): CgConfig { lastMove: opts.lastMove, orientation: ctrl.bottomColor(), coordinates: pref.coords !== Prefs.Coords.Hidden, + coordinatesOnSquares: pref.coords === Prefs.Coords.All, addPieceZIndex: pref.is3d, addDimensionsCssVarsTo: document.body, viewOnly: false, diff --git a/ui/analyse/src/keyboard.ts b/ui/analyse/src/keyboard.ts index fb3a1c2e38d4d..f242493612354 100644 --- a/ui/analyse/src/keyboard.ts +++ b/ui/analyse/src/keyboard.ts @@ -1,6 +1,7 @@ import * as control from './control'; import AnalyseCtrl from './ctrl'; import * as xhr from 'common/xhr'; +import { snabDialog } from 'common/dialog'; import { VNode } from 'snabbdom'; export const bind = (ctrl: AnalyseCtrl) => { @@ -135,7 +136,7 @@ export const bind = (ctrl: AnalyseCtrl) => { }; export function view(ctrl: AnalyseCtrl): VNode { - return site.dialog.snab({ + return snabDialog({ class: 'help.keyboard-help', htmlUrl: xhr.url('/analysis/help', { study: !!ctrl.study }), onClose() { diff --git a/ui/analyse/src/practice/practiceCtrl.ts b/ui/analyse/src/practice/practiceCtrl.ts index 4f5de45835de6..2cbf9d4c868c2 100644 --- a/ui/analyse/src/practice/practiceCtrl.ts +++ b/ui/analyse/src/practice/practiceCtrl.ts @@ -4,7 +4,7 @@ import { detectThreefold } from '../nodeFinder'; import { tablebaseGuaranteed } from '../explorer/explorerCtrl'; import AnalyseCtrl from '../ctrl'; import { Redraw } from '../interfaces'; -import { defined, prop, Prop } from 'common'; +import { defined, prop, Prop, requestIdleCallback } from 'common'; import { altCastles } from 'chess'; import { parseUci } from 'chessops/util'; import { makeSan } from 'chessops/san'; @@ -195,7 +195,7 @@ export function make(root: AnalyseCtrl, playableDepth: () => number): PracticeCt checkCevalOrTablebase(); } - site.requestIdleCallback(checkCevalOrTablebase, 800); + requestIdleCallback(checkCevalOrTablebase, 800); return { onCeval: checkCeval, diff --git a/ui/analyse/src/serverSideUnderboard.ts b/ui/analyse/src/serverSideUnderboard.ts index e056205db2a74..383b26f371678 100644 --- a/ui/analyse/src/serverSideUnderboard.ts +++ b/ui/analyse/src/serverSideUnderboard.ts @@ -5,7 +5,9 @@ import { url as xhrUrl, textRaw as xhrTextRaw } from 'common/xhr'; import { AnalyseData } from './interfaces'; import { ChartGame, AcplChart } from 'chart'; import { stockfishName } from 'common/spinner'; +import { domDialog } from 'common/dialog'; import { FEN } from 'chessground/types'; +import { escapeHtml } from 'common'; export default function (element: HTMLElement, ctrl: AnalyseCtrl) { $(element).replaceWith(ctrl.opts.$underboard); @@ -147,14 +149,14 @@ export default function (element: HTMLElement, ctrl: AnalyseCtrl) { // uglier in the process. const url = `${baseUrl()}/embed/game/${data.game.id}?theme=auto&bg=auto${location.hash}`; const iframe = ``; - site.dialog.dom({ + domDialog({ show: 'modal', htmlText: '
' + $(this).html() + '

' + '
' +
-        site.escapeHtml(iframe) +
+        escapeHtml(iframe) +
         '

' + iframe + '

' + diff --git a/ui/analyse/src/study/chapterEditForm.ts b/ui/analyse/src/study/chapterEditForm.ts index 55fb53b54793e..f739bc8bd26fc 100644 --- a/ui/analyse/src/study/chapterEditForm.ts +++ b/ui/analyse/src/study/chapterEditForm.ts @@ -4,6 +4,7 @@ import { spinnerVdom as spinner } from 'common/spinner'; import { option, emptyRedButton } from '../view/util'; import { ChapterMode, EditChapterData, Orientation, StudyChapterConfig, ChapterPreview } from './interfaces'; import { defined, prop } from 'common'; +import { snabDialog } from 'common/dialog'; import { h, VNode } from 'snabbdom'; import { Redraw } from '../interfaces'; import { StudySocketSend } from '../socket'; @@ -57,7 +58,7 @@ export function view(ctrl: StudyChapterEditForm): VNode | undefined { const data = ctrl.current(), noarg = ctrl.trans.noarg; return data - ? site.dialog.snab({ + ? snabDialog({ class: 'edit-' + data.id, // full redraw when changing chapter onClose() { ctrl.current(null); diff --git a/ui/analyse/src/study/chapterNewForm.ts b/ui/analyse/src/study/chapterNewForm.ts index 90241fdd2aa8c..85c669ac264e1 100644 --- a/ui/analyse/src/study/chapterNewForm.ts +++ b/ui/analyse/src/study/chapterNewForm.ts @@ -1,5 +1,6 @@ import { parseFen } from 'chessops/fen'; import { defined, prop, Prop, toggle } from 'common'; +import { snabDialog } from 'common/dialog'; import * as licon from 'common/licon'; import { bind, bindSubmit, onInsert, looseH as h, dataIcon } from 'common/snabbdom'; import { storedProp } from 'common/storage'; @@ -126,7 +127,7 @@ export function view(ctrl: StudyChapterNewForm): VNode { : 'normal'; const noarg = trans.noarg; - return site.dialog.snab({ + return snabDialog({ class: 'chapter-new', onClose() { ctrl.isOpen(false); diff --git a/ui/analyse/src/study/gamebook/gamebookEdit.ts b/ui/analyse/src/study/gamebook/gamebookEdit.ts index 94a670ce84c10..19fc064b7d0b2 100644 --- a/ui/analyse/src/study/gamebook/gamebookEdit.ts +++ b/ui/analyse/src/study/gamebook/gamebookEdit.ts @@ -1,5 +1,6 @@ import * as control from '../../control'; import AnalyseCtrl from '../../ctrl'; +import { requestIdleCallback } from 'common'; import * as licon from 'common/licon'; import throttle from 'common/throttle'; import { iconTag, bind, MaybeVNodes } from 'common/snabbdom'; @@ -27,7 +28,7 @@ export function render(ctrl: AnalyseCtrl): VNode { () => { study.commentForm.start(study.vm.chapterId, ctrl.path, ctrl.node); study.vm.toolTab('comments'); - site.requestIdleCallback( + requestIdleCallback( () => $('#comment-text').each(function (this: HTMLTextAreaElement) { this.focus(); diff --git a/ui/analyse/src/study/interfaces.ts b/ui/analyse/src/study/interfaces.ts index f025d570e1f24..e67cf397bfd2b 100644 --- a/ui/analyse/src/study/interfaces.ts +++ b/ui/analyse/src/study/interfaces.ts @@ -175,6 +175,7 @@ export interface ChapterPreviewBase { name: string; status?: StatusStr; lastMove?: string; + check?: '+' | '#'; } export interface ChapterPreviewFromServer extends ChapterPreviewBase { diff --git a/ui/analyse/src/study/inviteForm.ts b/ui/analyse/src/study/inviteForm.ts index 4e6acb992792c..fcaadd6e87007 100644 --- a/ui/analyse/src/study/inviteForm.ts +++ b/ui/analyse/src/study/inviteForm.ts @@ -6,6 +6,7 @@ import { prop, Prop } from 'common'; import { StudyMemberMap } from './interfaces'; import { AnalyseSocketSend } from '../socket'; import { storedSet, StoredSet } from 'common/storage'; +import { snabDialog } from 'common/dialog'; export interface StudyInviteFormCtrl { open: Prop; @@ -58,7 +59,7 @@ export function view(ctrl: ReturnType): VNode { const candidates = [...new Set([...ctrl.spectators(), ...ctrl.previouslyInvited()])] .filter(s => !ctrl.members()[titleNameToId(s)]) // remove existing members .sort(); - return site.dialog.snab({ + return snabDialog({ class: 'study__invite', onClose() { ctrl.open(false); diff --git a/ui/analyse/src/study/multiBoard.ts b/ui/analyse/src/study/multiBoard.ts index 9bffc5185e7de..3dc56c0eee772 100644 --- a/ui/analyse/src/study/multiBoard.ts +++ b/ui/analyse/src/study/multiBoard.ts @@ -117,6 +117,13 @@ const renderPlayingToggle = (ctrl: MultiBoardCtrl): MaybeVNode => ctrl.trans.noarg('playing'), ]); +const previewToCgConfig = (cp: ChapterPreview): CgConfig => ({ + fen: cp.fen, + lastMove: uciToMove(cp.lastMove), + turnColor: fenColor(cp.fen), + check: !!cp.check, +}); + const makePreview = (basePath: string, current: ChapterId, cloudEval?: MultiCloudEval) => (preview: ChapterPreview) => { const orientation = preview.orientation || 'white'; @@ -137,11 +144,10 @@ const makePreview = insert(vnode) { const el = vnode.elm as HTMLElement; vnode.data!.cg = site.makeChessground(el, { + ...previewToCgConfig(preview), coordinates: false, viewOnly: true, - fen: preview.fen, orientation, - lastMove: uciToMove(preview.lastMove), drawable: { enabled: false, visible: false, @@ -151,10 +157,7 @@ const makePreview = }, postpatch(old, vnode) { if (old.data!.fen !== preview.fen) { - old.data!.cg?.set({ - fen: preview.fen, - lastMove: uciToMove(preview.lastMove), - }); + old.data!.cg?.set(previewToCgConfig(preview)); } vnode.data!.fen = preview.fen; vnode.data!.cg = old.data!.cg; @@ -169,31 +172,42 @@ const makePreview = }; export const verticalEvalGauge = (chap: ChapterPreview, cloudEval: MultiCloudEval): MaybeVNode => - h( - 'span.mini-game__gauge', - { - attrs: { 'data-id': chap.id }, - hook: { - ...onInsert(cloudEval.observe), - postpatch(old, vnode) { - const prevNodeCloud: CloudEval | undefined = old.data?.cloud; - const cev = cloudEval.getCloudEval(chap.fen) || prevNodeCloud; - if (cev?.chances != prevNodeCloud?.chances) { - const elm = vnode.elm as HTMLElement; - (elm.firstChild as HTMLElement).style.height = `${Math.round( - ((1 - (cev?.chances || 0)) / 2) * 100, - )}%`; - if (cev) { - elm.title = renderScoreAtDepth(cev); - elm.classList.add('mini-game__gauge--set'); - } - } - vnode.data!.cloud = cev; + chap.check == '#' + ? h( + 'span.mini-game__gauge.mini-game__gauge--set', + { attrs: { 'data-id': chap.id, title: 'Checkmate' } }, + [ + h('span.mini-game__gauge__black', { + attrs: { style: `height: ${fenColor(chap.fen) == 'white' ? 100 : 0}%` }, + }), + h('tick'), + ], + ) + : h( + 'span.mini-game__gauge', + { + attrs: { 'data-id': chap.id }, + hook: { + ...onInsert(cloudEval.observe), + postpatch(old, vnode) { + const elm = vnode.elm as HTMLElement; + const prevNodeCloud: CloudEval | undefined = old.data?.cloud; + const cev = cloudEval.getCloudEval(chap.fen) || prevNodeCloud; + if (cev?.chances != prevNodeCloud?.chances) { + (elm.firstChild as HTMLElement).style.height = `${Math.round( + ((1 - (cev?.chances || 0)) / 2) * 100, + )}%`; + if (cev) { + elm.title = renderScoreAtDepth(cev); + elm.classList.add('mini-game__gauge--set'); + } + } + vnode.data!.cloud = cev; + }, + }, }, - }, - }, - [h('span.mini-game__gauge__black'), h('tick')], - ); + [h('span.mini-game__gauge__black'), h('tick')], + ); const renderUser = (player: ChapterPreviewPlayer): VNode => h('span.mini-game__user', [ diff --git a/ui/analyse/src/study/relay/interfaces.ts b/ui/analyse/src/study/relay/interfaces.ts index 4a0d89a0a04e4..6d2937b001f6e 100644 --- a/ui/analyse/src/study/relay/interfaces.ts +++ b/ui/analyse/src/study/relay/interfaces.ts @@ -6,6 +6,7 @@ export interface RelayData { isSubscribed?: boolean; // undefined if anon videoUrls?: [string, string]; pinned?: { userId: string; name: string; image?: string }; + lcc?: boolean; } export interface RelayGroup { @@ -29,16 +30,27 @@ export interface RelayRound { startsAt?: number; } +export interface RelayTourInfo { + format?: string; + tc?: string; + players?: string; +} + +export type RelayTourDates = [number] | [number, number]; + export interface RelayTour { id: string; name: string; slug: string; - description: string; + description?: string; + info: RelayTourInfo; official?: boolean; markup?: string; image?: string; teamTable?: boolean; leaderboard?: boolean; + tier?: number; + dates?: RelayTourDates; } export interface RelaySync { diff --git a/ui/analyse/src/study/relay/relayCtrl.ts b/ui/analyse/src/study/relay/relayCtrl.ts index 52c56504d66b2..2527f9227ef52 100644 --- a/ui/analyse/src/study/relay/relayCtrl.ts +++ b/ui/analyse/src/study/relay/relayCtrl.ts @@ -8,8 +8,9 @@ import RelayLeaderboard from './relayLeaderboard'; import { StudyChapters } from '../studyChapters'; import { MultiCloudEval } from '../multiCloudEval'; import { onWindowResize as videoPlayerOnWindowResize } from './videoPlayerView'; +import RelayStats from './relayStats'; -export const relayTabs = ['overview', 'boards', 'teams', 'leaderboard'] as const; +export const relayTabs = ['overview', 'boards', 'teams', 'leaderboard', 'stats'] as const; export type RelayTab = (typeof relayTabs)[number]; export default class RelayCtrl { @@ -21,6 +22,7 @@ export default class RelayCtrl { tab: Prop; teams?: RelayTeams; leaderboard?: RelayLeaderboard; + stats: RelayStats; streams: [string, string][] = []; showStreamerMenu = toggle(false); @@ -49,6 +51,7 @@ export default class RelayCtrl { this.leaderboard = data.tour.leaderboard ? new RelayLeaderboard(data.tour.id, this.federations, redraw) : undefined; + this.stats = new RelayStats(this.currentRound(), redraw); setInterval(() => this.redraw(true), 1000); const pinned = data.pinned; @@ -100,7 +103,7 @@ export default class RelayCtrl { const r = round || this.currentRound(); return `/broadcast/${this.data.tour.slug}/${r.slug}/${r.id}`; }; - + roundUrlWithHash = (round?: RelayRound) => `${this.roundPath(round)}#${this.tab()}`; updateAddressBar = (tourUrl: string, roundUrl: string) => { const url = this.tourShow() ? `${tourUrl}${this.tab() === 'overview' ? '' : `#${this.tab()}`}` : roundUrl; // when jumping from a tour tab to another page, remember which tour tab we were on. @@ -149,7 +152,7 @@ export default class RelayCtrl { }, 4500); this.redraw(); if (event.error) { - if (this.data.sync.log.slice(-2).every(e => e.error)) site.sound.play('error'); + if (this.data.sync.log.slice(-3).every(e => e.error)) site.sound.play('error'); console.warn(`relay synchronisation error: ${event.error}`); } }, diff --git a/ui/analyse/src/study/relay/relayManagerView.ts b/ui/analyse/src/study/relay/relayManagerView.ts index 7cc2bd15db661..1886b7630cd33 100644 --- a/ui/analyse/src/study/relay/relayManagerView.ts +++ b/ui/analyse/src/study/relay/relayManagerView.ts @@ -17,7 +17,7 @@ export default function (ctrl: RelayCtrl, study: StudyCtrl): MaybeVNode { h('span.text', { attrs: dataIcon(licon.RadioTower) }, 'Broadcast manager'), h('a', { attrs: { href: `/broadcast/round/${ctrl.id}/edit`, 'data-icon': licon.Gear } }), ]), - sync?.url || sync?.ids || sync?.urls ? (sync.ongoing ? stateOn : stateOff)(ctrl) : null, + sync?.url || sync?.ids || sync?.urls ? (sync.ongoing ? stateOn : stateOff)(ctrl) : statePush(), renderLog(ctrl), ]) : undefined, @@ -57,20 +57,17 @@ function stateOn(ctrl: RelayCtrl) { 'div.state.on.clickable', { hook: bind('click', _ => ctrl.setSync(false)), attrs: dataIcon(licon.ChasingArrows) }, [ - h( - 'div', - url - ? [ - sync.delay ? `Connected with ${sync.delay}s delay` : 'Connected to source', - h('br'), - url.replace(/https?:\/\//, ''), - ] + h('div', [ + 'Connected ', + sync?.delay ? `with ${sync.delay}s delay ` : null, + ...(url + ? ['to source', h('br'), url.replace(/https?:\/\//, '')] : ids - ? ['Connected to', h('br'), ids.length, ' game(s)'] + ? ['to', h('br'), ids.length, ' game(s)'] : urls - ? ['Connected to', h('br'), urls.length, ' urls'] - : [], - ), + ? ['to', h('br'), urls.length, ' sources'] + : []), + ]), ], ); } @@ -82,6 +79,9 @@ const stateOff = (ctrl: RelayCtrl) => [h('div.fat', 'Click to connect')], ); +const statePush = () => + h('div.state.push', { attrs: dataIcon(licon.UploadCloud) }, ['Listening to Broadcaster App']); + const dateFormatter = memoize(() => window.Intl && Intl.DateTimeFormat ? new Intl.DateTimeFormat(document.documentElement.lang, { diff --git a/ui/analyse/src/study/relay/relayStats.ts b/ui/analyse/src/study/relay/relayStats.ts new file mode 100644 index 0000000000000..bbaa05e3e0b37 --- /dev/null +++ b/ui/analyse/src/study/relay/relayStats.ts @@ -0,0 +1,39 @@ +import { Redraw } from 'common/snabbdom'; +import { spinnerVdom as spinner } from 'common/spinner'; +import { RelayRound } from './interfaces'; +import * as xhr from 'common/xhr'; +import { h } from 'snabbdom'; + +export default class RelayStats { + data?: any; + + constructor( + readonly round: RelayRound, + private readonly redraw: Redraw, + ) {} + + loadFromXhr = async () => { + this.data = await xhr.json(`/broadcast/round/${this.round.id}/stats`); + this.redraw(); + await site.asset.loadEsm('chart.relayStats', { + init: { + ...this.data, + round: this.round, + }, + }); + }; +} + +export const statsView = (ctrl: RelayStats) => + h( + 'div.relay-tour__stats', + { + class: { loading: !ctrl.data }, + hook: { + insert: _ => { + ctrl.loadFromXhr(); + }, + }, + }, + ctrl.data ? h('canvas') : [spinner()], + ); diff --git a/ui/analyse/src/study/relay/relayTourView.ts b/ui/analyse/src/study/relay/relayTourView.ts index e79ddb1317860..f20cc26a61758 100644 --- a/ui/analyse/src/study/relay/relayTourView.ts +++ b/ui/analyse/src/study/relay/relayTourView.ts @@ -4,19 +4,22 @@ import * as licon from 'common/licon'; import { bind, dataIcon, onInsert, looseH as h } from 'common/snabbdom'; import { VNode } from 'snabbdom'; import { innerHTML } from 'common/richText'; -import { RelayGroup, RelayRound } from './interfaces'; +import { RelayData, RelayGroup, RelayRound, RelayTourDates, RelayTourInfo } from './interfaces'; import { view as multiBoardView } from '../multiBoard'; -import { defined } from 'common'; +import { defined, memoize } from 'common'; import StudyCtrl from '../studyCtrl'; import { toggle } from 'common/controls'; import * as xhr from 'common/xhr'; import { teamsView } from './relayTeams'; +import { statsView } from './relayStats'; import { makeChat, type RelayViewContext } from '../../view/components'; import { gamesList } from './relayGames'; import { renderStreamerMenu, renderPinnedImage } from './relayView'; import { renderVideoPlayer } from './videoPlayerView'; import { leaderboardView } from './relayLeaderboard'; import { gameLinksListener } from '../studyChapters'; +import { copyMeInput } from 'common/copyMe'; +import { baseUrl } from '../../view/util'; export function renderRelayTour(ctx: RelayViewContext): VNode | undefined { const tab = ctx.relay.tab(); @@ -27,6 +30,8 @@ export function renderRelayTour(ctx: RelayViewContext): VNode | undefined { ? games(ctx) : tab == 'teams' ? teams(ctx) + : tab == 'stats' + ? stats(ctx) : leaderboard(ctx); return h('div.box.relay-tour', content); @@ -98,14 +103,85 @@ const leaderboard = (ctx: RelayViewContext) => [ ctx.relay.leaderboard && leaderboardView(ctx.relay.leaderboard), ]; -const overview = (ctx: RelayViewContext) => [ - ...header(ctx), - ctx.relay.data.tour.markup - ? h('div.relay-tour__markup', { - hook: innerHTML(ctx.relay.data.tour.markup, () => ctx.relay.data.tour.markup!), - }) - : h('div.relay-tour__markup', ctx.relay.data.tour.description), -]; +const showInfo = (i: RelayTourInfo, dates?: RelayTourDates) => { + const contents = [ + ['dates', dates && showDates(dates), 'objects.spiral-calendar'], + ['format', i.format, 'objects.crown'], + ['tc', i.tc, 'objects.mantelpiece-clock'], + ['players', i.players, 'activity.sparkles'], + ] + .map( + ([key, value, icon]) => + value && + icon && + h('div.relay-tour__info__' + key, [h('img', { attrs: { src: site.asset.flairSrc(icon) } }), value]), + ) + .filter(defined); + return contents.length ? h('div.relay-tour__info', contents) : undefined; +}; + +const dateFormat = memoize(() => + window.Intl && Intl.DateTimeFormat + ? new Intl.DateTimeFormat(site.displayLocale, { + month: 'short', + day: '2-digit', + } as any).format + : (d: Date) => d.toLocaleDateString(), +); + +const showDates = (dates: RelayTourDates) => { + const rendered = dates.map(date => dateFormat()(new Date(date))); + // if the tournament only lasts one day, don't show the end date + return rendered[1] ? `${rendered[0]} - ${rendered[1]}` : rendered[0]; +}; + +const showSource = (data: RelayData) => + data.lcc + ? h('div.relay-tour__source', [ + 'PGN source: ', + h('a', { attrs: { href: 'https://www.livechesscloud.com' } }, 'LiveChessCloud'), + ]) + : undefined; + +const overview = (ctx: RelayViewContext) => { + const tour = ctx.relay.data.tour; + return [ + ...header(ctx), + showInfo(tour.info, tour.dates), + tour.markup + ? h('div.relay-tour__markup', { + hook: innerHTML(tour.markup, () => tour.markup!), + }) + : undefined, + showSource(ctx.relay.data), + h('div.relay-tour__share', [ + h('h2.text', { attrs: dataIcon(licon.Heart) }, 'Sharing is caring'), + ...[ + [tour.name, ctx.relay.tourPath()], + [ctx.study.data.name, ctx.relay.roundPath()], + [ + `${ctx.study.data.name} PGN`, + `${ctx.relay.roundPath()}.pgn`, + h('div.form-help', [ + 'A public, real-time PGN source for this round. We also offer a ', + h( + 'a', + { attrs: { href: 'https://lichess.org/api#tag/Broadcasts/operation/broadcastStreamRoundPgn' } }, + 'streaming API', + ), + ' for faster and more efficient synchronisation.', + ]), + ], + ].map(([i18n, path, help]: [string, string, VNode]) => + h('div.form-group', [ + h('label.form-label', ctx.ctrl.trans.noarg(i18n)), + copyMeInput(`${baseUrl()}${path}`), + help, + ]), + ), + ]), + ]; +}; const groupSelect = (relay: RelayCtrl, group: RelayGroup) => { const toggle = relay.groupSelectShow; @@ -176,7 +252,7 @@ const roundSelect = (relay: RelayCtrl, study: StudyCtrl) => { }, relay.data.rounds.map(round => h(`tr.mselect__item${round.id == study.data.id ? '.current-round' : ''}`, [ - h('td.name', h('a', { attrs: { href: relay.roundPath(round) } }, round.name)), + h('td.name', h('a', { attrs: { href: relay.roundUrlWithHash(round) } }, round.name)), h('td.time', round.startsAt ? site.dateFormat()(new Date(round.startsAt)) : '-'), h( 'td.status', @@ -197,6 +273,7 @@ const roundSelect = (relay: RelayCtrl, study: StudyCtrl) => { const games = (ctx: RelayViewContext) => [ ...header(ctx), ctx.study.chapters.list.looksNew() ? undefined : multiBoardView(ctx.study.multiBoard, ctx.study), + showSource(ctx.relay.data), ]; const teams = (ctx: RelayViewContext) => [ @@ -204,6 +281,8 @@ const teams = (ctx: RelayViewContext) => [ ctx.relay.teams && teamsView(ctx.relay.teams, ctx.study.chapters.list), ]; +const stats = (ctx: RelayViewContext) => [...header(ctx), statsView(ctx.relay.stats)]; + const header = (ctx: RelayViewContext) => { const { ctrl, relay, allowVideo } = ctx; const d = relay.data, @@ -285,13 +364,7 @@ const makeTabs = (ctrl: AnalyseCtrl) => { makeTab('boards', 'Boards'), relay.teams && makeTab('teams', 'Teams'), relay.data.tour.leaderboard ? makeTab('leaderboard', 'Leaderboard') : undefined, - // study.members.myMember() - // ? h( - // 'a.text', - // { attrs: { ...dataIcon(licon.LineGraph), href: `/broadcast/${relay.data.tour.id}/stats` } }, - // 'Popularity stats', - // ) - // : undefined, + study.members.myMember() && relay.data.tour.tier ? makeTab('stats', 'Stats') : undefined, ]); }; diff --git a/ui/analyse/src/study/serverEval.ts b/ui/analyse/src/study/serverEval.ts index 3cbfdfa25f664..08ae909af4af4 100644 --- a/ui/analyse/src/study/serverEval.ts +++ b/ui/analyse/src/study/serverEval.ts @@ -1,6 +1,7 @@ import * as licon from 'common/licon'; import { bind, onInsert } from 'common/snabbdom'; import { spinnerVdom, chartSpinner } from 'common/spinner'; +import { requestIdleCallback } from 'common'; import { h, VNode } from 'snabbdom'; import AnalyseCtrl from '../ctrl'; import { ChartGame, AcplChart } from 'chart'; @@ -40,7 +41,7 @@ export function view(ctrl: ServerEval): VNode { const mainline = ctrl.requested ? ctrl.root.data.treeParts : ctrl.analysedMainline(); const chart = h('canvas.study__server-eval.ready.' + analysis.id, { hook: onInsert(el => { - site.requestIdleCallback(async () => { + requestIdleCallback(async () => { (await site.asset.loadEsm('chart.game')) .acpl(el as HTMLCanvasElement, ctrl.root.data, mainline, ctrl.root.trans) .then(chart => (ctrl.chart = chart)); diff --git a/ui/analyse/src/study/studyChapters.ts b/ui/analyse/src/study/studyChapters.ts index 5a0134c293ba1..15be731c77667 100644 --- a/ui/analyse/src/study/studyChapters.ts +++ b/ui/analyse/src/study/studyChapters.ts @@ -98,6 +98,7 @@ export default class StudyChaptersCtrl { if (onRelayPath || !d.relayPath) { cp.fen = node.fen; cp.lastMove = node.uci; + cp.check = node.san?.includes('#') ? '#' : node.san?.includes('+') ? '+' : undefined; } if (onRelayPath) { cp.lastMoveAt = Date.now(); diff --git a/ui/analyse/src/study/studyCtrl.ts b/ui/analyse/src/study/studyCtrl.ts index df4fe42f4a42e..6866c4091c0ea 100644 --- a/ui/analyse/src/study/studyCtrl.ts +++ b/ui/analyse/src/study/studyCtrl.ts @@ -702,7 +702,7 @@ export default class StudyCtrl { this.setMemberActive(d.w); if (d.s && !this.vm.mode.sticky) this.vm.behind++; if (d.s) this.data.position = d.p; - else if (d.w && d.w.s === site.sri) { + if (d.w?.s === site.sri) { this.vm.mode.write = this.relayData ? this.relayRecProp() : this.nonRelayRecMapProp(this.data.id); this.vm.chapterId = d.p.chapterId; } diff --git a/ui/analyse/src/study/studyForm.ts b/ui/analyse/src/study/studyForm.ts index 5be70dac7909e..bf9ccf3600186 100644 --- a/ui/analyse/src/study/studyForm.ts +++ b/ui/analyse/src/study/studyForm.ts @@ -1,6 +1,7 @@ import { VNode } from 'snabbdom'; import * as licon from 'common/licon'; import { prop } from 'common'; +import { snabDialog } from 'common/dialog'; import { bindSubmit, bindNonPassive, onInsert, looseH as h } from 'common/snabbdom'; import { emptyRedButton } from '../view/util'; import { StudyData } from './interfaces'; @@ -81,7 +82,7 @@ export function view(ctrl: StudyForm): VNode { ['member', ctrl.trans.noarg('members')], ['everyone', ctrl.trans.noarg('everyone')], ]; - return site.dialog.snab({ + return snabDialog({ class: 'study-edit', onClose() { ctrl.open(false); diff --git a/ui/analyse/src/study/studySearch.ts b/ui/analyse/src/study/studySearch.ts index bafaac6712070..546d139339504 100644 --- a/ui/analyse/src/study/studySearch.ts +++ b/ui/analyse/src/study/studySearch.ts @@ -1,6 +1,7 @@ import { Prop, Toggle, propWithEffect, toggle } from 'common'; import * as licon from 'common/licon'; import { bind, dataIcon, onInsert } from 'common/snabbdom'; +import { snabDialog } from 'common/dialog'; import { h, VNode } from 'snabbdom'; import { Redraw } from '../interfaces'; import { ChapterPreview } from './interfaces'; @@ -49,7 +50,7 @@ const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // export function view(ctrl: SearchCtrl) { const cleanQuery = ctrl.cleanQuery(); const highlightRegex = cleanQuery && new RegExp(escapeRegExp(cleanQuery), 'gi'); - return site.dialog.snab({ + return snabDialog({ class: 'study-search', onClose() { ctrl.open(false); diff --git a/ui/analyse/src/study/studyShare.ts b/ui/analyse/src/study/studyShare.ts index 8c31e0f848870..27bf036164c58 100644 --- a/ui/analyse/src/study/studyShare.ts +++ b/ui/analyse/src/study/studyShare.ts @@ -186,8 +186,8 @@ export function view(ctrl: StudyShare): VNode { h('form.form3', [ ...(ctrl.relay ? [ - ['broadcastUrl', ctrl.relay.tourPath()], - ['currentRoundUrl', ctrl.relay.roundPath()], + [ctrl.relay.data.tour.name, ctrl.relay.tourPath()], + [ctrl.data.name, ctrl.relay.roundPath()], ['currentGameUrl', addPly(`${ctrl.relay.roundPath()}/${chapter.id}`), true], ] : [ diff --git a/ui/analyse/src/study/topics.ts b/ui/analyse/src/study/topics.ts index 57137124dac62..c723939182667 100644 --- a/ui/analyse/src/study/topics.ts +++ b/ui/analyse/src/study/topics.ts @@ -1,6 +1,7 @@ import { prop } from 'common'; import { bind, bindSubmit, onInsert } from 'common/snabbdom'; import * as xhr from 'common/xhr'; +import { snabDialog } from 'common/dialog'; import { h, VNode } from 'snabbdom'; import { Redraw } from '../interfaces'; import { Topic } from './interfaces'; @@ -36,7 +37,7 @@ export const view = (ctrl: StudyCtrl): VNode => let tagify: Tagify | undefined; export const formView = (ctrl: TopicsCtrl, userId?: string): VNode => - site.dialog.snab({ + snabDialog({ class: 'study-topics', onClose() { ctrl.open(false); diff --git a/ui/analyse/src/view/actionMenu.ts b/ui/analyse/src/view/actionMenu.ts index ba41ec680f93e..c54571c3336e0 100644 --- a/ui/analyse/src/view/actionMenu.ts +++ b/ui/analyse/src/view/actionMenu.ts @@ -1,6 +1,7 @@ import { isEmpty } from 'common'; import * as licon from 'common/licon'; import { isTouchDevice } from 'common/device'; +import { domDialog } from 'common/dialog'; import { bind, dataIcon, MaybeVNodes, looseH as h } from 'common/snabbdom'; import { VNode } from 'snabbdom'; import { AutoplayDelay } from '../autoplay'; @@ -130,9 +131,7 @@ export function view(ctrl: AnalyseCtrl): VNode { h( 'a', { - hook: bind('click', () => - site.dialog.dom({ cash: $('.continue-with.g_' + d.game.id), show: 'modal' }), - ), + hook: bind('click', () => domDialog({ cash: $('.continue-with.g_' + d.game.id), show: 'modal' })), attrs: dataIcon(licon.Swords), }, noarg('continueFromHere'), diff --git a/ui/bits/css/build/bits.account.scss b/ui/bits/css/build/bits.account.scss index bede02bec5ebd..fc6680e347e51 100644 --- a/ui/bits/css/build/bits.account.scss +++ b/ui/bits/css/build/bits.account.scss @@ -3,4 +3,5 @@ @import '../../../common/css/form/form3'; @import '../../../common/css/form/radio'; @import '../../../common/css/form/emoji-picker'; +@import '../../../common/css/form/password'; @import '../account'; diff --git a/ui/bits/css/build/bits.auth.scss b/ui/bits/css/build/bits.auth.scss index 6aae96927a3d9..487fae78396be 100644 --- a/ui/bits/css/build/bits.auth.scss +++ b/ui/bits/css/build/bits.auth.scss @@ -1,4 +1,5 @@ @import '../../../common/css/plugin'; @import '../../../common/css/form/form3'; @import '../../../common/css/form/captcha'; +@import '../../../common/css/form/password'; @import '../auth'; diff --git a/ui/bits/css/build/bits.coach.editor.scss b/ui/bits/css/build/bits.coach.editor.scss index 34151ec4d096a..c0c6cc4ad7354 100644 --- a/ui/bits/css/build/bits.coach.editor.scss +++ b/ui/bits/css/build/bits.coach.editor.scss @@ -3,4 +3,3 @@ @import '../../../common/css/form/image'; @import '../../../common/css/component/btn-rack'; @import '../coach/editor'; -@import '../coach/picture'; diff --git a/ui/bits/css/coach/_editor.scss b/ui/bits/css/coach/_editor.scss index 31af0981bd6ba..2852083611627 100644 --- a/ui/bits/css/coach/_editor.scss +++ b/ui/bits/css/coach/_editor.scss @@ -1,7 +1,10 @@ @import 'layout'; -.coach-edit, -.coach-picture { +.coach-edit-picture { + @extend %image-preview-and-upload; +} + +.coach-edit { .top { > span { @extend %flex-between; @@ -21,14 +24,7 @@ margin-inline-start: auto; } } - - .picture_wrap { - @extend %image-preview-and-upload, %box-neat; - } } -} - -.coach-edit { .overview { width: 100%; display: flex; diff --git a/ui/bits/css/coach/_picture.scss b/ui/bits/css/coach/_picture.scss deleted file mode 100644 index 491a8e8c0d4cf..0000000000000 --- a/ui/bits/css/coach/_picture.scss +++ /dev/null @@ -1,24 +0,0 @@ -.coach-picture { - .picture_wrap { - text-align: center; - } - - form { - padding: 2em var(---box-padding); - border-top: $border; - text-align: center; - } - - form.delete button { - color: red; - } - - .cancel { - padding: 40px; - border-top: $border; - } - - .forms > *:first-child { - margin-top: 40px; - } -} diff --git a/ui/bits/css/forum/_forum.scss b/ui/bits/css/forum/_forum.scss index 83be09d0e1122..63851641867e1 100644 --- a/ui/bits/css/forum/_forum.scss +++ b/ui/bits/css/forum/_forum.scss @@ -42,3 +42,9 @@ margin-bottom: 1em; } } + +.forum-mod-feed { + tbody { + @extend %break-word; + } +} diff --git a/ui/bits/css/relay/_card.scss b/ui/bits/css/relay/_card.scss index 7f046141c6538..b651255c7f569 100644 --- a/ui/bits/css/relay/_card.scss +++ b/ui/bits/css/relay/_card.scss @@ -72,16 +72,23 @@ font-weight: bold; } &__title { + @include line-clamp(2); + margin: 0.2em 0 0.3em 0; font-weight: bold; color: $c-font-clearer; - font-size: 1.2em; + font-size: 1.3em; .relay-cards--tier-best & { font-size: 1.4em; } - padding: 0.2em 0 0.3em 0; } &__desc { @extend %roboto; + @include line-clamp(2); color: $c-font-dim; } + &__errors { + @extend %break-word, %nowrap-ellipsis; + font-family: monospace; + color: $c-bad; + } } diff --git a/ui/bits/css/relay/_form.scss b/ui/bits/css/relay/_form.scss index ea2fb0d1e2b7f..d0868ef5073c6 100644 --- a/ui/bits/css/relay/_form.scss +++ b/ui/bits/css/relay/_form.scss @@ -29,13 +29,7 @@ } } -#form3-grouping, -#form3-players, -#form3-teams { - font-family: monospace; -} - -.flash-box { +.relay-form__lcc-deprecated { border: 5px solid $m-brag_bg--mix-70; &::before { @@ -45,21 +39,40 @@ } } +.relay-form__contact-us.flash-box { + border: 5px solid $m-secondary_bg--mix-70; + + &::before { + color: $c-secondary; + content: $licon-UploadCloud; + font-size: 4em; + } +} + +.flash-round-push { + border: 5px solid $c-good; + background: $c-bg-box; + + &::before { + color: $c-good; + content: $licon-Checkmark; + font-size: 4em; + } +} + .relay-image { max-width: 100%; height: auto; } .relay-image-edit { - @extend %image-preview-and-upload, %box-neat; - margin-bottom: 2em; + @extend %image-preview-and-upload; } .relay-pinned-streamer-edit { - @extend %image-preview-and-upload, %box-neat; - > div { - flex-basis: 300px; - } + @extend %image-preview-and-upload; + background: none; + padding: 0; span { @extend %flex-between; } @@ -88,5 +101,8 @@ margin-left: 2em; letter-spacing: 0px; } + .button { + margin-inline-end: 1em; + } } } diff --git a/ui/bits/css/relay/_relay.scss b/ui/bits/css/relay/_relay.scss index 3b89ee2287f73..0147b28f292d9 100644 --- a/ui/bits/css/relay/_relay.scss +++ b/ui/bits/css/relay/_relay.scss @@ -9,6 +9,15 @@ margin: 2em 0 1em 0; } } +.relay-index__admin { + h2 { + margin: 0 0 1em 0; + color: $c-bad; + } + padding-bottom: 1em; + border-bottom: $border; + margin-bottom: 2em; +} .relay-image { &--fallback { diff --git a/ui/bits/css/ublog/_card.scss b/ui/bits/css/ublog/_card.scss index 1490fd232d48e..fe96360bbc67b 100644 --- a/ui/bits/css/ublog/_card.scss +++ b/ui/bits/css/ublog/_card.scss @@ -1,7 +1,7 @@ .ublog-post-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(22em, 1fr)); - grid-gap: 2em; + grid-gap: 1em; } .ublog-post-card { @extend %box-neat-force; @@ -68,22 +68,22 @@ } &__content { - padding: 3% 3% 4% 6%; + padding: 4% 3% 4% 6%; display: block; max-height: 12em; } &__title { + @include line-clamp(2); font-size: 1.3em; color: $c-font-clear; - display: block; } &__intro { @extend %roboto, %break-word; + @include line-clamp(2); color: $c-font; - margin-top: 1em; - display: block; + margin-top: 0.5em; } } diff --git a/ui/bits/css/ublog/_form.scss b/ui/bits/css/ublog/_form.scss index a88600ba199f0..259dc584921d3 100644 --- a/ui/bits/css/ublog/_form.scss +++ b/ui/bits/css/ublog/_form.scss @@ -4,41 +4,19 @@ &__main .form-split, &__main > .form-group, &__main .form-actions, - &__delete .form-actions, - &__image { + &__delete .form-actions { padding-left: var(---box-padding); padding-right: var(---box-padding); } - &__image-text { - background: $c-bg-zebra; - margin-bottom: 5vh; - border-bottom: $border; - font-size: 0.9em; - .form-group { - display: none; - } - &.visible { - height: auto; - .form-group { - display: block; - } - } + .form-fieldset { + @extend %box-margin-horiz; } &__delete { - padding: 10em 0 5em 0; + padding-top: 10em; } &__etiquette { margin-inline-end: 3em; } - &__image { - .form-group { - @extend %flex-column; - justify-content: center; - } - img { - @extend %box-neat; - } - } &__etiquette { @extend %box-neat; background: $c-bg-zebra; diff --git a/ui/bits/css/user/_show.scss b/ui/bits/css/user/_show.scss index 39864cfd5d664..abbeba0b7bb02 100644 --- a/ui/bits/css/user/_show.scss +++ b/ui/bits/css/user/_show.scss @@ -93,9 +93,10 @@ } &:hover { > a { - @extend %box-radius-top, %dropdown-shadow; + @extend %box-radius-top; background: $c-bg-header-dropdown; color: $c-font-clear; + box-shadow: -1px 5px 6px rgba(0, 0, 0, 0.3); } .dropdown-window { visibility: visible; diff --git a/ui/bits/package.json b/ui/bits/package.json index cc1cbc6100f36..4dfd6503ed721 100644 --- a/ui/bits/package.json +++ b/ui/bits/package.json @@ -72,7 +72,6 @@ "src/bits.publicChats.ts", "src/bits.qrcode.ts", "src/bits.relayForm.ts", - "src/bits.relayStats.ts", "src/bits.soundMove.ts", "src/bits.streamer.ts", "src/bits.team.ts", diff --git a/ui/bits/src/bits.account.ts b/ui/bits/src/bits.account.ts index a187550c826b8..9dab290051bb2 100644 --- a/ui/bits/src/bits.account.ts +++ b/ui/bits/src/bits.account.ts @@ -1,5 +1,6 @@ import * as licon from 'common/licon'; import * as xhr from 'common/xhr'; +import { addPasswordVisibilityToggleListener } from 'common/password'; import flairPicker from './load/flairPicker'; site.load.then(() => { @@ -7,6 +8,8 @@ site.load.then(() => { flairPicker(this); }); + addPasswordVisibilityToggleListener(); + const localPrefs: [string, string, string, boolean][] = [ ['behavior', 'arrowSnap', 'arrow.snap', true], ['behavior', 'courtesy', 'courtesy', false], diff --git a/ui/bits/src/bits.checkout.ts b/ui/bits/src/bits.checkout.ts index b5df753870893..8ab608bb2e2fd 100644 --- a/ui/bits/src/bits.checkout.ts +++ b/ui/bits/src/bits.checkout.ts @@ -145,6 +145,7 @@ const payPalStyle = { }; function payPalOrderStart($checkout: Cash, pricing: Pricing, getAmount: () => number | undefined) { + if (!window.paypalOrder) return; (window.paypalOrder as any) .Buttons({ style: payPalStyle, @@ -173,6 +174,7 @@ function payPalOrderStart($checkout: Cash, pricing: Pricing, getAmount: () => nu } function payPalSubscriptionStart($checkout: Cash, pricing: Pricing, getAmount: () => number | undefined) { + if (!window.paypalSubscription) return; (window.paypalSubscription as any) .Buttons({ style: payPalStyle, diff --git a/ui/bits/src/bits.cropDialog.ts b/ui/bits/src/bits.cropDialog.ts index 05c51a7385979..7f89eca2a5788 100644 --- a/ui/bits/src/bits.cropDialog.ts +++ b/ui/bits/src/bits.cropDialog.ts @@ -1,4 +1,5 @@ import { defined } from 'common'; +import { domDialog } from 'common/dialog'; import Cropper from 'cropperjs'; export interface CropOpts { @@ -61,17 +62,17 @@ export async function initModule(o?: CropOpts) { minContainerHeight: viewBounds.height, }); - const dlg = await site.dialog.dom({ + const dlg = await domDialog({ class: 'crop-viewer', - css: [{ themed: 'bits.cropDialog' }, { url: 'npm/cropper.min.css' }], + css: [{ hashed: 'bits.cropDialog' }, { url: 'npm/cropper.min.css' }], htmlText: `

Crop image to desired shape

`, - append: [{ selector: '.crop-view', node: container }], - action: [ - { selector: '.dialog-actions > .cancel', action: d => d.close() }, - { selector: '.dialog-actions > .submit', action: crop }, + append: [{ where: '.crop-view', node: container }], + actions: [ + { selector: '.dialog-actions > .cancel', listener: d => d.close() }, + { selector: '.dialog-actions > .submit', listener: crop }, ], onClose: () => { URL.revokeObjectURL(url); diff --git a/ui/bits/src/bits.diagnosticDialog.ts b/ui/bits/src/bits.diagnosticDialog.ts index c0935b0300809..d377fd0a31d4f 100644 --- a/ui/bits/src/bits.diagnosticDialog.ts +++ b/ui/bits/src/bits.diagnosticDialog.ts @@ -1,5 +1,7 @@ import { isTouchDevice } from 'common/device'; +import { domDialog } from 'common/dialog'; import * as licon from 'common/licon'; +import { escapeHtml } from 'common'; export async function initModule() { const ops = processQueryParams(); @@ -13,7 +15,7 @@ export async function initModule() { `Engine: ${site.storage.get('ceval.engine')}, ` + `Threads: ${site.storage.get('ceval.threads')}` + (logs ? `\n\n${logs}` : ''); - const escaped = site.escapeHtml(text); + const escaped = escapeHtml(text); const flash = ops > 0 ? `

Changes applied

` : ''; const submit = document.body.dataset.user ? `
@@ -22,9 +24,9 @@ export async function initModule() { : ''; const clear = logs ? `` : ''; const copy = ``; - const dlg = await site.dialog.dom({ + const dlg = await domDialog({ class: 'diagnostic', - css: [{ themed: 'bits.diagnosticDialog' }], + css: [{ hashed: 'bits.diagnosticDialog' }], htmlText: `

Diagnostics

${flash}
${escaped}
diff --git a/ui/bits/src/bits.forum.ts b/ui/bits/src/bits.forum.ts index b8aafc9a492e8..2a9a6fbb8cf25 100644 --- a/ui/bits/src/bits.forum.ts +++ b/ui/bits/src/bits.forum.ts @@ -1,4 +1,5 @@ import * as xhr from 'common/xhr'; +import { domDialog } from 'common/dialog'; import { Textcomplete } from '@textcomplete/core'; import { TextareaEditor } from '@textcomplete/textarea'; @@ -6,24 +7,34 @@ site.load.then(() => { $('.forum') .on('click', 'a.delete', function (this: HTMLAnchorElement) { const link = this; - site.dialog - .dom({ - cash: $('.forum-delete-modal'), - attrs: { view: { action: link.href } }, - }) - .then(dlg => { - $(dlg.view) - .find('form') - .attr('action', link.href) - .on('submit', function (this: HTMLFormElement, e: Event) { - e.preventDefault(); - xhr.formToXhr(this); - $(link).closest('.forum-post').hide(); - dlg.close(); - }); - $(dlg.view).find('form button.cancel').on('click', dlg.close); - dlg.showModal(); - }); + domDialog({ + cash: $('.forum-delete-modal'), + attrs: { view: { action: link.href } }, + }).then(dlg => { + $(dlg.view) + .find('form') + .attr('action', link.href) + .on('submit', function (this: HTMLFormElement, e: Event) { + e.preventDefault(); + xhr.formToXhr(this); + $(link).closest('.forum-post').hide(); + dlg.close(); + }); + $(dlg.view).find('form button.cancel').on('click', dlg.close); + dlg.showModal(); + }); + return false; + }) + .on('click', 'a.mod-relocate', function (this: HTMLAnchorElement) { + const link = this; + domDialog({ + cash: $('.forum-relocate-modal'), + attrs: { view: { action: link.href } }, + }).then(dlg => { + $(dlg.view).find('form').attr('action', link.href); + $(dlg.view).find('form button.cancel').on('click', dlg.close); + dlg.showModal(); + }); return false; }) .on('click', 'form.unsub button', function (this: HTMLButtonElement) { diff --git a/ui/bits/src/bits.hcaptcha.ts b/ui/bits/src/bits.hcaptcha.ts index 9a7729193c4f8..6c3f0b12bba58 100644 --- a/ui/bits/src/bits.hcaptcha.ts +++ b/ui/bits/src/bits.hcaptcha.ts @@ -4,7 +4,7 @@ export function initModule() { if ('credentialless' in window && window.crossOriginIsolated) { const documentCreateElement = document.createElement; - script.src = 'https://hcaptcha.com/1/api.js?onload=initHcaptcha'; + script.src = 'https://hcaptcha.com/1/api.js?onload=initHcaptcha&recaptchacompat=off'; script.onload = () => { document.createElement = function () { const element = documentCreateElement.apply(this, arguments as any); diff --git a/ui/bits/src/bits.login.ts b/ui/bits/src/bits.login.ts index 901119e530ffa..f007e8fcd0dea 100644 --- a/ui/bits/src/bits.login.ts +++ b/ui/bits/src/bits.login.ts @@ -1,10 +1,14 @@ import * as xhr from 'common/xhr'; import debounce from 'common/debounce'; +import { addPasswordVisibilityToggleListener } from 'common/password'; import { storedJsonProp } from 'common/storage'; -export function initModule(mode: 'login' | 'signup') { - mode === 'login' ? loginStart() : signupStart(); +export function initModule(mode: 'login' | 'signup' | 'reset') { + mode === 'login' ? loginStart() : mode === 'signup' ? signupStart() : resetStart(); + + addPasswordVisibilityToggleListener(); } + class LoginHistory { historyStorage = storedJsonProp('login.history', () => []); private now = () => Math.round(Date.now() / 1000); @@ -25,7 +29,6 @@ function loginStart() { const toggleSubmit = ($submit: Cash, v: boolean) => $submit.prop('disabled', !v).toggleClass('disabled', !v); - (function load() { const form = document.querySelector(selector) as HTMLFormElement, $f = $(form); @@ -110,3 +113,7 @@ function signupStart() { site.asset.loadEsm('bits.passwordComplexity', { init: 'form3-password' }); } + +function resetStart() { + site.asset.loadEsm('bits.passwordComplexity', { init: 'form3-newPasswd1' }); +} diff --git a/ui/bits/src/bits.publicChats.ts b/ui/bits/src/bits.publicChats.ts index da0d520eefea9..0d9819cd2962b 100644 --- a/ui/bits/src/bits.publicChats.ts +++ b/ui/bits/src/bits.publicChats.ts @@ -1,4 +1,5 @@ import { text, form } from 'common/xhr'; +import { domDialog } from 'common/dialog'; site.load.then(() => { let autoRefreshEnabled = true; @@ -39,7 +40,7 @@ site.load.then(() => { $('#communication').on('click', '.line:not(.lichess)', function (this: HTMLDivElement) { const $l = $(this); - site.dialog.dom({ cash: $('.timeout-modal') }).then(dlg => { + domDialog({ cash: $('.timeout-modal') }).then(dlg => { $('.username', dlg.view).text($l.find('.user-link').text()); $('.text', dlg.view).text($l.text().split(' ').slice(1).join(' ')); $('.button', dlg.view).on('click', function (this: HTMLButtonElement) { diff --git a/ui/bits/src/bits.relayStats.ts b/ui/bits/src/bits.relayStats.ts deleted file mode 100644 index 04fd2c9080fcb..0000000000000 --- a/ui/bits/src/bits.relayStats.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function initModule(data: any) { - console.log(data); -} diff --git a/ui/bits/src/bits.tourForm.ts b/ui/bits/src/bits.tourForm.ts index 55e7ab615e681..70b9d6e9be5ec 100644 --- a/ui/bits/src/bits.tourForm.ts +++ b/ui/bits/src/bits.tourForm.ts @@ -8,11 +8,6 @@ site.load.then(() => { $variant.on('change', showPosition); showPosition(); - $('form .conditions a.show').on('click', function (this: HTMLAnchorElement) { - $(this).remove(); - $('form .conditions').addClass('visible'); - }); - $('.flatpickr').each(function (this: HTMLInputElement) { flatpickr(this, { minDate: 'today', diff --git a/ui/ceval/src/util.ts b/ui/ceval/src/util.ts index e07387d7389e4..884bfc5848f78 100644 --- a/ui/ceval/src/util.ts +++ b/ui/ceval/src/util.ts @@ -1,5 +1,6 @@ import { isMobile } from 'common/device'; -import { memoize } from 'common/common'; +import { memoize, escapeHtml } from 'common'; +import { domDialog } from 'common/dialog'; export function isEvalBetter(a: Tree.ClientEval, b: Tree.ClientEval): boolean { return a.depth > b.depth || (a.depth === b.depth && a.nodes > b.nodes); @@ -35,28 +36,26 @@ export const sharedWasmMemory = (lo: number, hi = 32767): WebAssembly.Memory => export function showEngineError(engine: string, error: string) { console.log(error); - site.dialog - .dom({ - class: 'engine-error', - htmlText: - `

${site.escapeHtml(engine)} error

` + - (error.includes('Status 503') - ? `

Your external engine does not appear to be connected.

+ domDialog({ + class: 'engine-error', + htmlText: + `

${escapeHtml(engine)} error

` + + (error.includes('Status 503') + ? `

Your external engine does not appear to be connected.

Please check the network and restart your provider if possible.

` - : `${site.escapeHtml(error)}

Things to try

    + : `${escapeHtml(error)}

    Things to try

    • Decrease memory slider in engine settings
    • Clear site settings for lichess.org
    • Select another engine
    • Update your browser
    `), - }) - .then((dlg: Dialog) => { - const select = () => - setTimeout(() => { - const range = document.createRange(); - range.selectNodeContents(dlg.view.querySelector('.err')!); - window.getSelection()?.removeAllRanges(); - window.getSelection()?.addRange(range); - }, 0); - dlg.view.querySelector('.err')?.addEventListener('focus', select); - dlg.showModal(); - }); + }).then(dlg => { + const select = () => + setTimeout(() => { + const range = document.createRange(); + range.selectNodeContents(dlg.view.querySelector('.err')!); + window.getSelection()?.removeAllRanges(); + window.getSelection()?.addRange(range); + }, 0); + dlg.view.querySelector('.err')?.addEventListener('focus', select); + dlg.showModal(); + }); } diff --git a/ui/ceval/src/view/main.ts b/ui/ceval/src/view/main.ts index 637571be7cacf..75f115d27eb8a 100644 --- a/ui/ceval/src/view/main.ts +++ b/ui/ceval/src/view/main.ts @@ -2,7 +2,7 @@ import * as winningChances from '../winningChances'; import * as licon from 'common/licon'; import { stepwiseScroll } from 'common/scroll'; import { onInsert, bind, LooseVNodes, looseH as h } from 'common/snabbdom'; -import { defined, notNull } from 'common'; +import { defined, notNull, requestIdleCallback } from 'common'; import { ParentCtrl, NodeEvals, CevalState } from '../types'; import { VNode } from 'snabbdom'; import { Position } from 'chessops/chess'; @@ -309,7 +309,7 @@ function getElPvMoves(e: TouchEvent | MouseEvent): (string | null)[] { } function checkHover(el: HTMLElement, ceval: CevalCtrl): void { - site.requestIdleCallback( + requestIdleCallback( () => ceval.setHovering(getElFen(el), $(el).find('div.pv:hover').attr('data-uci') || undefined), 500, ); diff --git a/ui/challenge/css/_challenge.scss b/ui/challenge/css/_challenge.scss index 23a843ef62beb..4a8ab2b12cccf 100644 --- a/ui/challenge/css/_challenge.scss +++ b/ui/challenge/css/_challenge.scss @@ -1,7 +1,7 @@ #challenge-app { @extend %box-radius-left, %dropdown-shadow; overflow: hidden; - width: 270px; + width: 300px; text-align: center; .empty { @@ -32,7 +32,7 @@ .buttons { @extend %flex-between-nowrap; - + align-items: stretch; @media (hover: hover) { display: none; } @@ -47,7 +47,17 @@ width: 33%; } + a.view { + font-size: 1.5rem; + color: $c-primary; + &:hover { + background: $c-primary; + color: $c-over; + } + } + button { + border-radius: 0; cursor: pointer; color: $c-good; width: 100%; @@ -88,7 +98,8 @@ } } - button::before { + button::before, + a.view::before { line-height: 3rem; } @@ -126,9 +137,11 @@ display: block; font-weight: bold; margin-bottom: 0.1em; + .user-link { margin-inline-start: -5px; } + signal { margin-inline-start: 5px; } diff --git a/ui/challenge/css/_page.scss b/ui/challenge/css/_page.scss index a71e709c74c21..d970764d2d660 100644 --- a/ui/challenge/css/_page.scss +++ b/ui/challenge/css/_page.scss @@ -56,6 +56,7 @@ .correspondence-waiting { font-size: 1.5em; margin: 2em auto; + &::before { color: $c-good; } @@ -67,8 +68,8 @@ text-align: center; } - .details { - @extend %flex-between, %box-neat; + .details-wrapper { + @extend %box-neat; ---font: #{$c-secondary}; ---bg: #{$m-secondary_bg--mix-10}; @@ -84,34 +85,58 @@ background: var(---bg); border: 1px solid var(---font); - > div { - flex: 0 1 auto; - @extend %flex-center, %roboto; + .content { + @extend %flex-between; - &::before { - color: var(---font); - font-size: 6rem; - margin-inline-end: 0.2em; + > div { + flex: 0 1 auto; + @extend %flex-center, %roboto; + + &::before { + color: var(---font); + font-size: 6rem; + margin-inline-end: 0.2em; - @media (max-width: at-most($xx-small)) { - display: none; + @media (max-width: at-most($xx-small)) { + display: none; + } + } + + div { + line-height: 1.4; } - } - div { - line-height: 1.4; + .clock { + font-weight: bold; + } } - .clock { + .mode { + text-align: end; font-weight: bold; + font-size: 0.8em; + color: var(---font); } } - .mode { - text-align: end; - font-weight: bold; - font-size: 0.8em; - color: var(---font); + .rules { + margin-top: 1.5rem; + font-size: 1rem; + + > div { + @extend %flex-center; + gap: 1em; + margin-top: 1em; + + .challenge-rule { + @extend %flex-center; + + .icon-flair { + height: 1.5em; + margin: 0.2em 0.8em 0 0; + } + } + } } } diff --git a/ui/challenge/src/interfaces.ts b/ui/challenge/src/interfaces.ts index b4a818fe74c82..2f7d160c3cbdf 100644 --- a/ui/challenge/src/interfaces.ts +++ b/ui/challenge/src/interfaces.ts @@ -37,6 +37,7 @@ export interface Challenge { status: ChallengeStatus; challenger?: ChallengeUser; destUser?: ChallengeUser; + rules?: unknown[]; variant: Variant; initialFen: FEN; rated: boolean; diff --git a/ui/challenge/src/view.ts b/ui/challenge/src/view.ts index 6176094602553..558c72d4999a3 100644 --- a/ui/challenge/src/view.ts +++ b/ui/challenge/src/view.ts @@ -74,7 +74,8 @@ function challenge(ctrl: ChallengeCtrl, dir: ChallengeDirection) { } function inButtons(ctrl: ChallengeCtrl, c: Challenge): VNode[] { - return [ + const viewInsteadOfAccept = (c.rules?.length ?? 0) > 0; + const acceptElement = () => h('form', { attrs: { method: 'post', action: `/challenge/${c.id}/accept` } }, [ h('button.button.accept', { attrs: { @@ -85,7 +86,14 @@ function inButtons(ctrl: ChallengeCtrl, c: Challenge): VNode[] { }, hook: onClick(ctrl.onRedirect), }), - ]), + ]); + const viewElement = () => + h('a.view', { + attrs: { 'data-icon': licon.Eye, href: '/' + c.id, title: ctrl.trans('viewInFullSize') }, + }); + + return [ + viewInsteadOfAccept ? viewElement() : acceptElement(), h('button.button.decline', { attrs: { type: 'submit', 'data-icon': licon.X, title: ctrl.trans('decline') }, hook: onClick(() => ctrl.decline(c.id, 'generic')), diff --git a/ui/chart/package.json b/ui/chart/package.json index 47c82da99ac7f..72a26e03fc0da 100644 --- a/ui/chart/package.json +++ b/ui/chart/package.json @@ -25,7 +25,8 @@ "src/chart.ratingHistory.ts", "src/chart.game.ts", "src/chart.resizePolyfill.ts", - "src/chart.lag.ts" + "src/chart.lag.ts", + "src/chart.relayStats.ts" ] } } diff --git a/ui/chart/src/chart.game.ts b/ui/chart/src/chart.game.ts index 55bb802e02a43..2548fef632714 100644 --- a/ui/chart/src/chart.game.ts +++ b/ui/chart/src/chart.game.ts @@ -1,11 +1,11 @@ import { ChartGame, AcplChart } from './interface'; import movetime from './movetime'; import acpl from './acpl'; -import { gridColor, tooltipBgColor, fontFamily, maybeChart, resizePolyfill } from './common'; +import { gridColor, tooltipBgColor, fontFamily, maybeChart, resizePolyfill, colorSeries } from './common'; export { type ChartGame, type AcplChart }; -export { gridColor, tooltipBgColor, fontFamily, maybeChart, resizePolyfill }; +export { gridColor, colorSeries, tooltipBgColor, fontFamily, maybeChart, resizePolyfill }; export function initModule(): ChartGame { return { diff --git a/ui/chart/src/chart.ratingHistory.ts b/ui/chart/src/chart.ratingHistory.ts index 14046476a1c56..e15c9e0b3e6a1 100644 --- a/ui/chart/src/chart.ratingHistory.ts +++ b/ui/chart/src/chart.ratingHistory.ts @@ -71,14 +71,11 @@ const oneDay = 24 * 60 * 60 * 1000; const dateFormat = memoize(() => window.Intl && Intl.DateTimeFormat - ? new Intl.DateTimeFormat( - document.documentElement.lang.startsWith('ar-') ? 'ar-ly' : document.documentElement.lang, - { - month: 'short', - day: '2-digit', - year: 'numeric', - }, - ).format + ? new Intl.DateTimeFormat(site.displayLocale, { + month: 'short', + day: '2-digit', + year: 'numeric', + }).format : (d: Date) => d.toLocaleDateString(), ); diff --git a/ui/chart/src/chart.relayStats.ts b/ui/chart/src/chart.relayStats.ts new file mode 100644 index 0000000000000..114808da0209e --- /dev/null +++ b/ui/chart/src/chart.relayStats.ts @@ -0,0 +1,214 @@ +import { RoundStats } from './interface'; +import * as chart from 'chart.js'; +import 'chartjs-adapter-dayjs-4'; +import { hoverBorderColor, gridColor, tooltipBgColor, fontColor, fontFamily, animation } from './common'; +import { memoize } from 'common'; +import ChartDataLabels from 'chartjs-plugin-datalabels'; + +chart.Chart.register( + chart.PointElement, + chart.TimeScale, + chart.Tooltip, + chart.LinearScale, + chart.LineController, + chart.LineElement, + chart.Filler, + chart.Title, + ChartDataLabels, +); + +chart.Chart.defaults.font = fontFamily(); + +interface RelayChart extends chart.Chart { + updateData(d: RoundStats): void; +} + +const dateFormat = memoize(() => + window.Intl && Intl.DateTimeFormat + ? new Intl.DateTimeFormat(site.displayLocale, { + year: 'numeric', + month: 'short', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hour12: false, + }).format + : (d: Date) => d.toLocaleDateString(), +); + +export default function initModule(data: RoundStats) { + const $el = $('.relay-tour__stats canvas'); + makeChart($el, data); +} + +const makeDataset = (data: RoundStats, el: HTMLCanvasElement): chart.ChartDataset<'line'>[] => { + const blue = 'hsl(209, 76%, 56%)'; + const gradient = el.getContext('2d')?.createLinearGradient(0, 0, 0, 400); + gradient?.addColorStop(0, 'rgba(119, 152, 191, 0.4)'); + gradient?.addColorStop(1, 'rgba(119, 152, 191, 0.05)'); + const plot: chart.ChartDataset<'line'>[] = [ + { + indexAxis: 'x', + type: 'line', + data: fillData(data.viewers), + label: `${data.round.name}`, + pointBorderColor: '#fff', + pointBackgroundColor: blue, + backgroundColor: gradient, + fill: true, + borderColor: blue, + borderWidth: 2, + pointRadius: 0, + pointHoverRadius: 5, + hoverBorderColor: hoverBorderColor, + tension: 0, + datalabels: { display: false }, + }, + ]; + if (data.round.startsAt && data.viewers.length) { + const pink = 'hsl(317, 74%, 73%)'; + plot.push({ + indexAxis: 'x', + yAxisID: 'y2', + type: 'line', + data: [ + { x: data.round.startsAt, y: 0 }, + { x: data.round.startsAt, y: 100 }, + ], + borderColor: pink, + borderDash: [5, 5], + datalabels: { + align: 'top', + offset: -5, + display: 'auto', + formatter: (value: chart.Point) => (value.y == 0 ? '' : 'Round Start'), + color: pink, + }, + pointRadius: 0, + pointHoverRadius: 0, + }); + } + return plot; +}; + +const makeChart = ($el: Cash, data: RoundStats) => { + const ds = makeDataset(data, $el[0] as HTMLCanvasElement); + const config: chart.ChartConfiguration<'line'> = { + type: 'line', + data: { + datasets: ds, + }, + options: { + parsing: false, + interaction: { + mode: 'nearest', + axis: 'x', + intersect: false, + }, + locale: document.documentElement.lang, + maintainAspectRatio: false, + responsive: true, + animations: animation(500 / ds[0].data.length), + scales: { + x: { + type: 'time', + grid: { + color: gridColor, + }, + border: { + display: false, + }, + ticks: { + maxTicksLimit: 20, + major: { + enabled: true, + }, + }, + title: { + display: true, + text: 'Time', + color: fontColor, + }, + time: { + minUnit: 'minute', + }, + }, + y: { + type: 'linear', + grid: { + color: gridColor, + }, + border: { + display: false, + }, + ticks: { + stepSize: 1, + maxTicksLimit: 20, + }, + title: { + display: true, + text: 'Spectators', + color: fontColor, + }, + min: 0, + }, + y2: { + display: false, + }, + }, + plugins: { + tooltip: { + filter: i => i.datasetIndex == 0, + backgroundColor: tooltipBgColor, + bodyColor: fontColor, + titleColor: fontColor, + borderColor: fontColor, + borderWidth: 1, + caretPadding: 5, + usePointStyle: true, + callbacks: { + title: items => (items.length ? dateFormat()(items[0].parsed.x) : ''), + }, + }, + title: { + display: true, + text: data.viewers[0] ? titleText(data) : 'No viewership stats yet', + color: fontColor, + }, + }, + }, + }; + const relayChart = new chart.Chart($el[0] as HTMLCanvasElement, config) as RelayChart; + relayChart.updateData = (data: RoundStats) => { + relayChart.data.datasets = makeDataset(data, $el[0] as HTMLCanvasElement); + relayChart.options.plugins!.title!.text = titleText(data); + relayChart.options.animations = updateAnimations(data); + relayChart.update(); + }; + return relayChart; +}; + +const titleText = (data: RoundStats): string => + `${data.round.name} • Start - ${dateFormat()(data.round.startsAt)}`; + +const updateAnimations = (data?: RoundStats) => + data && data.viewers.length > 30 ? animation(1000 / data.viewers.length) : undefined; + +const fillData = (viewers: RoundStats['viewers']) => { + const points: chart.Point[] = []; + if (!viewers.length) return []; + const last = viewers[viewers.length - 1]; + points.push({ x: last[0], y: last[1] }); + viewers + .slice(0, viewers.length - 2) + .reverse() + .forEach(([behind, v]) => { + const minuteGap = points.find(({ x }) => x - behind <= 60); + if (!minuteGap) { + for (let i = behind; i < points[points.length - 1].x; i += 60) { + points.push({ x: i, y: v }); + } + } else points.push({ x: behind, y: v }); + }); + return points.map(p => ({ x: p.x * 1000, y: p.y })).reverse(); +}; diff --git a/ui/chart/src/common.ts b/ui/chart/src/common.ts index 10d12ddb83981..8d062a147e0f6 100644 --- a/ui/chart/src/common.ts +++ b/ui/chart/src/common.ts @@ -110,3 +110,16 @@ export function animation(duration: number): ChartOptions<'line'>['animations'] export function resizePolyfill() { if ('ResizeObserver' in window === false) site.asset.loadEsm('chart.resizePolyfill'); } +export const colorSeries = [ + '#2b908f', + '#90ee7e', + '#f45b5b', + '#7798BF', + '#aaeeee', + '#ff0066', + '#eeaaee', + '#55BF3B', + '#DF5353', + '#7798BF', + '#aaeeee', +]; diff --git a/ui/chart/src/interface.ts b/ui/chart/src/interface.ts index 374506df0b3fa..01600c06b4cca 100644 --- a/ui/chart/src/interface.ts +++ b/ui/chart/src/interface.ts @@ -72,3 +72,18 @@ export interface PerfRatingHistory { name: string; points: [number, number, number, number][]; } + +interface RelayRound { + id: string; + name: string; + slug: string; + finished?: boolean; + ongoing?: boolean; + createdAt?: number; + startsAt?: number; +} + +export interface RoundStats { + round: RelayRound; + viewers: [number, number][]; +} diff --git a/ui/chat/tests/spam.test.ts b/ui/chat/tests/spam.test.ts index 1d43e1d0b5aa7..366cbdbe15ed8 100644 --- a/ui/chat/tests/spam.test.ts +++ b/ui/chat/tests/spam.test.ts @@ -15,7 +15,7 @@ test('self report', () => { }, }); - const spy = vi.spyOn(xhr, 'text').mockImplementation((url: string, init?: RequestInit) => { + const spy = vi.spyOn(xhr, 'text').mockImplementation((url: string) => { expect(url).toBe('/jslog/abcdef123456?n=spam'); return Promise.resolve('ok'); }); diff --git a/ui/chess/src/glyphs.ts b/ui/chess/src/glyphs.ts index 0c3bf5cb6c77a..903b5963ad980 100644 --- a/ui/chess/src/glyphs.ts +++ b/ui/chess/src/glyphs.ts @@ -21,18 +21,13 @@ export function annotationShapes(node: Tree.Node): DrawShape[] { orig: destSquare, brush: prerendered ? '' : undefined, customSvg: prerendered ? { html: prerendered } : undefined, - label: prerendered ? undefined : { text: symbol, fill: fills[symbol] || 'purple' }, + label: prerendered ? undefined : { text: symbol, fill: 'purple' }, // keep some purple just to keep feedback forum on their toes }, ]; } else return []; } -const fills: { [key: string]: string } = { - '✓': '#168226', - '✗': '#df5353', -}; - // We can render glyphs as text, but people are used to these SVGs as the "Big 5" glyphs // and right now they look better const prependDropShadow = (svgBase: string) => @@ -80,4 +75,16 @@ const glyphToSvg: Dictionary = { `), + + // Correct move in a puzzle + '✓': prependDropShadow(` + + +`), + + // Incorrect move in a puzzle + '✗': prependDropShadow(` + + +`), }; diff --git a/ui/cli/src/cli.ts b/ui/cli/src/cli.ts index 03983a42d0efc..081a70d9625c9 100644 --- a/ui/cli/src/cli.ts +++ b/ui/cli/src/cli.ts @@ -1,4 +1,6 @@ import { load as loadDasher } from 'dasher'; +import { domDialog } from 'common/dialog'; +import { escapeHtml } from 'common'; export function initModule({ input }: { input: HTMLInputElement }) { site.asset.userComplete({ @@ -21,7 +23,8 @@ function execute(q: string) { // 5kr1/p1p2p2/2b2Q2/3q2r1/2p4p/2P4P/P2P1PP1/1R1K3R b - - 1 23 if (q.match(/^([1-8pnbrqk]+\/){7}.*/i)) return (location.href = '/analysis/standard/' + q.replace(/ /g, '_')); - location.href = '/@/' + q; + if (q.match(/^[a-zA-Z0-9_-]{2,30}$/)) location.href = '/@/' + q; + else location.href = '/player/search/' + q; } function command(q: string) { @@ -46,15 +49,15 @@ function commandHelp(aliases: string, args: string, desc: string) { '
    ' + aliases .split(' ') - .map(a => `

    ${a} ${site.escapeHtml(args)}

    `) + .map(a => `

    ${a} ${escapeHtml(args)}

    `) .join('') + `
    ${desc}
    ` ); } function help() { - site.dialog.dom({ - css: [{ themed: 'cli.help' }], + domDialog({ + css: [{ hashed: 'cli.help' }], class: 'clinput-help', show: 'modal', htmlText: diff --git a/ui/common/css/abstract/_mixins.scss b/ui/common/css/abstract/_mixins.scss index 07c6f60d44f5e..53c629d980f19 100644 --- a/ui/common/css/abstract/_mixins.scss +++ b/ui/common/css/abstract/_mixins.scss @@ -148,3 +148,11 @@ background: linear-gradient($rtl-dir, $c 0px, $c 5px, rgba(0, 0, 0, 0) 5px, rgba(0, 0, 0, 0) 100%); } } + +@mixin line-clamp($lines) { + display: -webkit-box; + -webkit-line-clamp: $lines; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/ui/common/css/base/_typography.scss b/ui/common/css/base/_typography.scss index b1f77d7c53e0a..2fcf61cdffcf3 100644 --- a/ui/common/css/base/_typography.scss +++ b/ui/common/css/base/_typography.scss @@ -18,7 +18,7 @@ h4 { } h1 { - @include fluid-size('font-size', 20px, 40px); + @include fluid-size('font-size', 19px, 38px); a { color: $c-link-dim; @@ -33,6 +33,10 @@ h2 { @include fluid-size('font-size', 16px, 30px); } +.monospace { + font-family: monospace; +} + .ninja-title { @extend %base-font; diff --git a/ui/common/css/base/_util.scss b/ui/common/css/base/_util.scss index 301a34ef24b35..80409a69ee1dc 100644 --- a/ui/common/css/base/_util.scss +++ b/ui/common/css/base/_util.scss @@ -40,7 +40,6 @@ bad { .drop-target { border: 2px dashed $c-font; - width: 100%; height: min-content; } diff --git a/ui/common/css/component/_flash.scss b/ui/common/css/component/_flash.scss index 6896606ac7bdd..cd5cb0e684a89 100644 --- a/ui/common/css/component/_flash.scss +++ b/ui/common/css/component/_flash.scss @@ -19,6 +19,10 @@ } } + &-success { + color: $c-over; + } + &-warning { background: $c-warn; color: $c-over; diff --git a/ui/common/css/component/_lichess-pgn-viewer.scss b/ui/common/css/component/_lichess-pgn-viewer.scss index 30f918240adda..d6e7b41bc12ae 100644 --- a/ui/common/css/component/_lichess-pgn-viewer.scss +++ b/ui/common/css/component/_lichess-pgn-viewer.scss @@ -10,6 +10,9 @@ --c-lpv-bg-pane: #{$m-bg-zebra2--fade-1}; --c-lpv-pgn-text: #{$c-bg-zebra2}; --c-lpv-font: #{$c-font}; + // TODO: erase or replace with `--c-lpv-past-moves` when lpv-pgn-viewer > 2.1.0 is out + --c-lpv-font-accent: #{$c-font}; + --c-lpv-font-shy: #{$c-font-dim}; --c-lpv-accent-over: #{$c-over}; --c-lpv-fbt-hover: #{$m-primary_bg--mix-75}; @@ -17,7 +20,7 @@ --c-lpv-current-move: #{$m-primary_bg--mix-70}; --c-lpv-move-hover: #{$m-primary_bg--mix-30}; --c-lpv-border: #{$c-border}; - --c-lpv-side-border: hsl(37deg, 5%, 13%); //#{$c-border-page}; + --c-lpv-side-border: #{$c-border-page}; --c-lpv-inaccuracy: #{$c-inaccuracy}; --c-lpv-mistake: #{$c-mistake}; --c-lpv-blunder: #{$c-blunder}; diff --git a/ui/common/css/component/_markdown.scss b/ui/common/css/component/_markdown.scss index cd0607f123c96..31f75e18e8f4b 100644 --- a/ui/common/css/component/_markdown.scss +++ b/ui/common/css/component/_markdown.scss @@ -168,8 +168,10 @@ @mixin rendered-markdown--embed { .embed { @extend %video; - margin: $block-gap auto; + > iframe { + left: 0; + } } .twitter-tweet { diff --git a/ui/common/css/form/_form3.scss b/ui/common/css/form/_form3.scss index 6b49d14c12ca6..36d60f1cc9d10 100644 --- a/ui/common/css/form/_form3.scss +++ b/ui/common/css/form/_form3.scss @@ -114,33 +114,48 @@ textarea.form-control { border-top: $border; } -.password-complexity { - margin-top: -2rem; - margin-bottom: 3rem; -} - -.password-complexity-meter { - display: flex; - grid-gap: 0.25rem; - height: 0.4rem; - margin-top: 1rem; - - > * { - background-color: gray; - width: 25%; - } -} - .form-fieldset { @extend %box-radius; + border: $border; + background: $c-bg-zebra; margin: 1rem 0 3rem 0; - border-color: rgba(128, 128, 128, 0.3); padding: 2rem 2rem 0 2rem; legend { - padding: 0 1em; - text-align: center; - font-style: italic; + @extend %box-radius; + background: $c-bg-zebra; + padding: 0.5em 1.5em; + text-align: right; font-size: 1.2em; + user-select: none; + } +} +.form-fieldset--toggle { + legend { + border-top: $border; + cursor: pointer; + &::after { + content: '▲'; + margin-left: 1ch; + } + } +} +.form-fieldset--toggle-off { + background: none; + border-width: 1px 0 0 0; + padding-top: 0; + legend { + border: $border; + background: none; + &:hover { + @extend %box-neat; + background: $c-bg-zebra; + } + &::after { + content: '▼'; + } + } + > *:not(legend) { + display: none; } } diff --git a/ui/common/css/form/_image.scss b/ui/common/css/form/_image.scss index 4f3e923f38cb0..2950b32395825 100644 --- a/ui/common/css/form/_image.scss +++ b/ui/common/css/form/_image.scss @@ -1,6 +1,5 @@ %image-preview-and-upload { @extend %flex-center; - padding: var(---box-padding); gap: var(---box-padding); background: $c-bg-zebra; img, diff --git a/ui/common/css/form/_password.scss b/ui/common/css/form/_password.scss new file mode 100644 index 0000000000000..59e64ebb777d8 --- /dev/null +++ b/ui/common/css/form/_password.scss @@ -0,0 +1,29 @@ +.password-complexity { + margin-top: -2rem; + margin-bottom: 3rem; +} +.password-complexity-meter { + display: flex; + grid-gap: 0.25rem; + height: 0.4rem; + margin-top: 1rem; + + > * { + background-color: gray; + width: 25%; + } +} + +.password-reveal { + @extend %button-none; + float: right; + margin-right: 1em; + margin-top: -2.2em; + + &:focus-visible { + outline: 2px solid $c-primary; + } +} +.password-reveal.revealed { + color: $c-bad; +} diff --git a/ui/common/css/header/_topnav-visible.scss b/ui/common/css/header/_topnav-visible.scss index 72dfad61ca3ae..c74b6b31d888f 100644 --- a/ui/common/css/header/_topnav-visible.scss +++ b/ui/common/css/header/_topnav-visible.scss @@ -95,7 +95,6 @@ div { visibility: visible; max-height: none; - padding-bottom: 8px; } } } diff --git a/ui/common/css/theme/_default.scss b/ui/common/css/theme/_default.scss index 1de52f1820327..71b4597e9eb74 100644 --- a/ui/common/css/theme/_default.scss +++ b/ui/common/css/theme/_default.scss @@ -70,7 +70,7 @@ $c-clearer: #fff; --c-metal-top-hover: #{hsl($site-hue, 7%, 25%)}; --c-metal-bottom-hover: #{hsl($site-hue, 5%, 22%)}; - --c-font-dim: #{change-color($c-font, $lightness: 55%)}; + --c-font-dim: #{change-color($c-font, $lightness: 58%)}; --c-font-dimmer: #{change-color($c-font, $lightness: 42%)}; --c-font-clear: #{change-color($c-font, $lightness: 80%)}; --c-font-clearer: #{change-color($c-font, $lightness: 89%)}; diff --git a/ui/common/src/common.ts b/ui/common/src/common.ts index df6b55c6f697a..8b7e50719766e 100644 --- a/ui/common/src/common.ts +++ b/ui/common/src/common.ts @@ -132,3 +132,18 @@ export function pushMap(m: SparseMap, key: string, val: T) { export function hyphenToCamel(str: string) { return str.replace(/-([a-z])/g, g => g[1].toUpperCase()); } + +export const requestIdleCallback = (f: () => void, timeout?: number) => { + if (window.requestIdleCallback) window.requestIdleCallback(f, timeout ? { timeout } : undefined); + else requestAnimationFrame(f); +}; + +export const escapeHtml = (str: string) => + /[&<>"']/.test(str) + ? str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/'/g, ''') + .replace(/"/g, '"') + : str; diff --git a/ui/site/src/dialog.ts b/ui/common/src/dialog.ts similarity index 62% rename from ui/site/src/dialog.ts rename to ui/common/src/dialog.ts index ab4815dc4fa5a..0d47f8afb1273 100644 --- a/ui/site/src/dialog.ts +++ b/ui/common/src/dialog.ts @@ -1,11 +1,54 @@ -import { onInsert, looseH as h, VNode } from 'common/snabbdom'; -import { isTouchDevice } from 'common/device'; -import * as xhr from 'common/xhr'; -import * as licon from 'common/licon'; +import { onInsert, looseH as h, VNode, Attrs, LooseVNodes } from './snabbdom'; +import { isTouchDevice } from './device'; +import * as xhr from './xhr'; +import * as licon from './licon'; let dialogPolyfill: { registerDialog: (dialog: HTMLDialogElement) => void }; -// for usage see file://./../../../@types/lichess/dialog.d.ts +export interface Dialog { + readonly open: boolean; // is visible? + readonly view: HTMLElement; // your content div + readonly returnValue?: 'ok' | 'cancel' | string; // how did we close? + + showModal(): Promise; // resolves on close + show(): Promise; // resolves on close + actions(actions?: Action | Action[]): void; // set new or reattach existing actions + close(): void; +} + +export interface DialogOpts { + class?: string; // zero or more classes for your view div + css?: ({ url: string } | { hashed: string })[]; // fetches hashed or full url css + htmlText?: string; // content, text will be used as-is + cash?: Cash; // content, overrides htmlText, will be cloned and any 'none' class removed + htmlUrl?: string; // content, overrides htmlText and cash, url will be xhr'd + append?: { node: HTMLElement; where?: string }[]; // if no where selector, appends to view + attrs?: { dialog?: Attrs; view?: Attrs }; // optional attrs for dialog and view div + actions?: Action | Action[]; // if present, add listeners to action buttons + onClose?: (dialog: Dialog) => void; // called when dialog closes + noCloseButton?: boolean; // if true, no upper right corner close button + noClickAway?: boolean; // if true, no click-away-to-close + noScrollable?: boolean; // if true, no scrollable div container. Fixes dialogs containing an auto-completer +} + +export interface DomDialogOpts extends DialogOpts { + parent?: Element; // for centering and dom placement, otherwise fixed on document.body + show?: 'modal' | boolean; // if not falsy, auto-show, and if 'modal' remove from dom on close +} + +//snabDialog automatically shows as 'modal' on redraw unless onInsert callback is supplied +export interface SnabDialogOpts extends DialogOpts { + vnodes?: LooseVNodes; // content, overrides other content properties + onInsert?: (dialog: Dialog) => void; // if supplied, call show() or showModal() manually +} + +export type ActionListener = (dialog: Dialog, action: Action, e: Event) => void; + +// Actions are managed listeners / results that are easily refreshed on DOM changes +// if no event is specified, then 'click' is assumed +export type Action = + | { selector: string; event?: string | string[]; listener: ActionListener } + | { selector: string; event?: string | string[]; result: string }; export const ready = site.load.then(async () => { window.addEventListener('resize', onResize); @@ -94,8 +137,9 @@ export function snabDialog(o: SnabDialogOpts): VNode { } class DialogWrapper implements Dialog { - private restore?: { focus: HTMLElement; overflow: string }; + private restore?: { focus?: HTMLElement; overflow: string }; private resolve?: (dialog: Dialog) => void; + private eventCleanup: { el: Element; type: string; listener: EventListener }[] = []; private observer: MutationObserver = new MutationObserver(list => { for (const m of list) if (m.type === 'childList') @@ -126,16 +170,10 @@ class DialogWrapper implements Dialog { dialog.querySelector('.close-button-anchor > .close-button')?.addEventListener('click', cancelOnInterval); if (!o.noClickAway) setTimeout(() => dialog.addEventListener('click', cancelOnInterval)); - for (const node of o.append ?? []) { - (node.selector ? view.querySelector(node.selector) : view)!.appendChild(node.node); + for (const app of o.append ?? []) { + (app.where ? view.querySelector(app.where) : view)?.appendChild(app.node); } - if (o.action) - for (const a of Array.isArray(o.action) ? o.action : [o.action]) { - view.querySelector(a.selector)?.addEventListener('click', () => { - if (!a.action || typeof a.action === 'string') this.close(a.action); - else a.action(this, a); - }); - } + this.actions(); } get open() { @@ -150,7 +188,32 @@ class DialogWrapper implements Dialog { this.dialog.returnValue = v; } + // attach/reattach existing listeners or provide a set of new ones + actions = (actions = this.o.actions) => { + for (const { el, type, listener } of this.eventCleanup) { + el.removeEventListener(type, listener); + } + this.eventCleanup = []; + if (!actions) return; + for (const a of Array.isArray(actions) ? actions : [actions]) { + for (const event of Array.isArray(a.event) ? a.event : a.event ? [a.event] : ['click']) { + for (const el of this.view.querySelectorAll(a.selector)) { + const listener = (e: Event) => { + if ('listener' in a) a.listener(this, a, e); + else this.close(a.result); + }; + this.eventCleanup.push({ el, type: event, listener }); + el.addEventListener(event, listener); + } + } + } + }; + show = (): Promise => { + this.restore = { + overflow: document.body.style.overflow, + }; + document.body.style.overflow = 'hidden'; this.returnValue = ''; this.dialog.show(); return new Promise(resolve => (this.resolve = resolve)); @@ -179,18 +242,20 @@ class DialogWrapper implements Dialog { private onRemove = () => { this.observer.disconnect(); if (!this.dialog.returnValue) this.dialog.returnValue = 'cancel'; - this.restore?.focus.focus(); // one modal at a time please + this.restore?.focus?.focus(); // one modal at a time please if (this.restore?.overflow !== undefined) document.body.style.overflow = this.restore.overflow; this.restore = undefined; this.resolve?.(this); this.o.onClose?.(this); this.dialog.remove(); + for (const css of this.o.css ?? []) + 'hashed' in css && site.asset.removeCssPath(css.hashed), 'url' in css && site.asset.removeCss(css.url); }; } function assets(o: DialogOpts) { const cssPromises = (o.css ?? []).map(css => { - if ('themed' in css) return site.asset.loadCssPath(css.themed); + if ('hashed' in css) return site.asset.loadCssPath(css.hashed); else if ('url' in css) return site.asset.loadCss(css.url); else return Promise.resolve(); }); diff --git a/ui/common/src/linkPopup.ts b/ui/common/src/linkPopup.ts index 49dd3eb287520..e9e74df2b53d2 100644 --- a/ui/common/src/linkPopup.ts +++ b/ui/common/src/linkPopup.ts @@ -1,3 +1,5 @@ +import { domDialog } from './dialog'; + export const makeLinkPopups = (dom: HTMLElement | Cash, trans: Trans, selector = 'a[href^="http"]') => { const $el = $(dom); if (!$el.hasClass('link-popup-ready')) @@ -10,11 +12,10 @@ export const onClick = (a: HTMLLinkElement, trans: Trans): boolean => { const url = new URL(a.href); if (isPassList(url)) return true; - site.dialog - .dom({ - class: 'link-popup', - css: [{ themed: 'bits.linkPopup' }], - htmlText: ` + domDialog({ + class: 'link-popup', + css: [{ hashed: 'bits.linkPopup' }], + htmlText: `