Merge branch 'master' into atomic_chess

* master: (29 commits)
  simul: move on on game end - fixes #179
  fix spectator link to player
  improve lobby playing games timer - resolves #187
  fallback language links for anon
  Revert "remove lang fallback links"
  fix dark language links
  hr "hrvatski" translation #11604. Author: gus_fring.
  th "ไทย" translation #11603. Author: Jer_King.
  Add Yoruba language
  yo "Yorùbá" translation #11588. Author: Mikhail04.
  sk "slovenčina" translation #11579. Author: xslyepov.
  hr "hrvatski" translation #11578. Author: DrOz.
  sr "Српски језик" translation #11568. Author: NoSurrenderNoRetreat.
  el "Ελληνικά" translation #11562. Author: kosf.
  pt "Português" translation #11561. Author: BearJr.
  ca "Català, valencià" translation #11560. Author: stanislaski.
  sk "slovenčina" translation #11557. Author: Inconnu_SVK.
  vi "Tiếng Việt" translation #11551. Author: mahabatnha.
  ar "العربية" translation #11549. Author: Abd0.
  pl "polski" translation #11535. Author: luk82.
  ...
This commit is contained in:
Thibault Duplessis 2015-01-02 13:04:11 +01:00
commit 7bfc208f04
34 changed files with 187 additions and 75 deletions

View file

@ -2,16 +2,32 @@ package controllers
import play.api.data.Form
import lila.app._
import lila.common.{ Captcha, LilaCookie }
import lila.i18n.{ Translation, TransInfo }
import lila.api.Context
import lila.app._
import lila.common.{ Captcha, LilaCookie, HTTPRequest }
import lila.i18n.{ Translation, TransInfo }
import views._
object I18n extends LilaController {
private def env = Env.i18n
def select = OpenBody { implicit ctx =>
import play.api.data.Forms._
import play.api.data._
implicit val req = ctx.body
Form(single("lang" -> text.verifying(env.pool contains _))).bindFromRequest.fold(
_ => notFound,
lang => (ctx.me ?? { me => lila.user.UserRepo.setLang(me.id, lang) }) inject Redirect {
s"${Env.api.Net.Protocol}${lang}.${Env.api.Net.Domain}" + {
HTTPRequest.referer(ctx.req).fold(routes.Lobby.home.url) { str =>
new java.net.URL(str).getPath
}
}
}
)
}
def contribute = Open { implicit ctx =>
val mines = (ctx.req.acceptLanguages map env.transInfos.get).toList.flatten.distinct
Ok(html.i18n.contribute(env.transInfos.all, mines)).fuccess

View file

@ -45,7 +45,11 @@ private[controllers] trait LilaController
Open(BodyParsers.parse.anyContent)(f)
protected def Open[A](p: BodyParser[A])(f: Context => Fu[Result]): Action[A] =
Action.async(p)(req => reqToCtx(req) flatMap f)
Action.async(p) { req =>
reqToCtx(req) flatMap { ctx =>
Env.i18n.requestHandler.forUser(req, ctx.me).fold(f(ctx))(fuccess)
}
}
protected def OpenBody(f: BodyContext => Fu[Result]): Action[AnyContent] =
OpenBody(BodyParsers.parse.anyContent)(f)
@ -62,7 +66,9 @@ private[controllers] trait LilaController
protected def Auth[A](p: BodyParser[A])(f: Context => UserModel => Fu[Result]): Action[A] =
Action.async(p) { req =>
reqToCtx(req) flatMap { implicit ctx =>
ctx.me.fold(authenticationFailed)(me => f(ctx)(me))
ctx.me.fold(authenticationFailed) { me =>
Env.i18n.requestHandler.forUser(req, ctx.me).fold(f(ctx)(me))(fuccess)
}
}
}

View file

@ -110,9 +110,9 @@ chessground: Boolean = true)(body: Html)(implicit ctx: Context)
<a class="toggle" href="#">
<span data-icon="u">&nbsp;@langName(lang)</span>
</a>
<ul class="language_links dropdown" data-url="@staticUrl("trans/refs.json")">
<form action="@routes.I18n.select" method="POST" class="language_links dropdown" data-url="@staticUrl("trans/refs.json")?v=@assetVersion">
<li><a href="@routes.I18n.contribute">Help translate Lichess!</a></li>
</ul>
</form>
</div>
@ctx.me.map { me =>
<div class="auth">
@ -172,7 +172,7 @@ chessground: Boolean = true)(body: Html)(implicit ctx: Context)
<a id="site_title" href="@routes.Lobby.home">
lichess<span class="extension">.org</span>
</a>
@if(acceptsLanguage(lang)) {
@if(ctx.isAuth || acceptsLanguage(lang)) {
@baseline
} else {
@langFallbackLinks

View file

@ -88,6 +88,7 @@ oneDay=يوم واحد
nbDays=%s يوم
nbHours=%s ساعة
time=الوقت
rating=التصنيف
username=إسم المستخدم
password=كلمة السر
haveAnAccount=هل لديك حساب؟

View file

@ -88,6 +88,7 @@ oneDay=Un dia
nbDays=%s dies
nbHours=%s hores
time=Temps
rating=Puntuació
username=Nom d'usuari
password=Contrasenya
haveAnAccount=Ja tens un compte?

View file

@ -88,6 +88,7 @@ oneDay=Μία μέρα
nbDays=%s μέρες
nbHours=%s ώρες
time=Χρόνος
rating=Κατάταξη
username=Όνομα χρήστη
password=Κωδικός
haveAnAccount=Έχετε λογαριασμό;

View file

@ -88,6 +88,7 @@ oneDay=یک روز
nbDays=روز %s
nbHours=%s ساعت
time=زمان
rating=ریتینگ
username=نام کاربری
password=رمزعبور
haveAnAccount=شما صاحب حساب کاربری می باشید؟

View file

@ -1,6 +1,6 @@
playWithAFriend=שחק עם חבר
playWithTheMachine=שחק עם המחשב
toInviteSomeoneToPlayGiveThisUrl=כדי להזמין מישהוא לשחק, תן את הכתובת הזאת
toInviteSomeoneToPlayGiveThisUrl=כדי להזמין מישהו לשחק, תן את הכתובת הזאת
gameOver=המשחק נגמר
waitingForOpponent=ממתין ליריב
waiting=ממתין
@ -88,6 +88,7 @@ oneDay=יום אחד
nbDays=ימים %s
nbHours=%s שעות
time=זמן
rating=דירוג
username=שם משתמש
password=סיסמה
haveAnAccount=יש לך חשבון?
@ -169,6 +170,7 @@ tournaments=טורנירים
tournamentPoints=נקודות טורניר
viewTournament=צפה בטורניר
backToTournament=בחזרה לטורניר
backToGame=חזרה למשחק
freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents=משחק שחמט חינם ברשת. שחק שחמט כעת בממשק נקי. ללא הרשמה, ללא פרסומות, ללא תוכנת הרחבה. שחק שחמט עם המחשב, חברים או יריבים אקראיים.
teams=קבוצות
nbMembers=%s חברים
@ -305,3 +307,5 @@ thisPuzzleIsCorrect=הפאזל נכון ומעניין.
thisPuzzleIsWrong=הפאזל לא נכון או משעמם
youHaveNbSecondsToMakeYourFirstMove=יש לך %s שניות לבצע את המהלך הראשון!
nbGamesInPlay=%s משחקים בתהליך
automaticallyProceedToNextGameAfterMoving=המשך אוטומטית למשחק הבא אחרי מהלך
autoSwitch=החלפה אוטומטית

View file

@ -88,6 +88,7 @@ oneDay=Jedan dan
nbDays=%s dana
nbHours=%s sati
time=Vrijeme
rating=Rejting
username=Korisničko ime
password=Zaporka
haveAnAccount=Imate li otvoren račun?

View file

@ -88,6 +88,7 @@ oneDay=Un giorno
nbDays=%s giorni
nbHours=%s ore
time=Tempo
rating=Punteggio
username=Username
password=Password
haveAnAccount=Hai un account?

View file

@ -88,6 +88,7 @@ oneDay=1 dzień na ruch
nbDays=%s Dni
nbHours=%s godzin
time=Czas
rating=Ranking
username=Nazwa użytkownika
password=Hasło
haveAnAccount=Masz już konto?

View file

@ -88,6 +88,7 @@ oneDay=Um dia
nbDays=%s dias
nbHours=%s horas
time=Tempo
rating=Rating
username=Nome
password=Palavra-chave
haveAnAccount=Tem uma conta?

View file

@ -88,6 +88,7 @@ oneDay=Jeden deň
nbDays=%s dní
nbHours=%s hodín
time=Čas
rating=Rating
username=Meno užívateľa
password=Heslo
haveAnAccount=Máte účet?

View file

@ -88,6 +88,7 @@ oneDay=një ditë
nbDays=%s ditë
nbHours=%s orë
time=Koha
rating=Vlerësuar
username=Emri i përdoruesit
password=Fjalëkalimi
haveAnAccount=Keni një llogari?

View file

@ -88,6 +88,7 @@ oneDay=Један дан
nbDays=%s дана
nbHours=%s сати
time=Време
rating=Рејтинг
username=Корисничко име
password=Лозинка
haveAnAccount=Имате налог?

View file

@ -88,6 +88,7 @@ oneDay=หนึ่งวัน
nbDays=%s วัน
nbHours=%s ชั่วโมง
time=เวลา
rating=ระดับคะแนน
username=ชื่อผู้ใช้
password=รหัสผ่าน
haveAnAccount=มีบัญชีแล้วหรือยัง?
@ -97,7 +98,7 @@ changeEmail=เปลี่ยนอีเมล
email=อีเมล
emailIsOptional=อีเมลเป็นตัวเลือกซึ่ง Lichess จะใช้เพื่อการตั้งรหัสผ่านของคุณใหม่ในกรณีที่คุณลืมมัน
passwordReset=ตั้งรหัสผ่านใหม่
forgotPassword=ลืมรหัสผ่านหรือเปล่า?
forgotPassword=ลืมรหัสผ่าน?
learnMoreAboutLichess=เรียนรู้เพิ่มเติมเกี่ยวกับ Lichess
rank=อันดับ
gamesPlayed=เกมที่ได้เล่น

View file

@ -88,6 +88,7 @@ oneDay=Một ngày
nbDays=%s ngày
nbHours=%s giờ
time=Thời gian
rating=Hệ số Elo
username=Tên đăng nhập
password=Mật khẩu
haveAnAccount=Bạn có tài khoản chưa?

38
conf/messages.yo Normal file
View file

@ -0,0 +1,38 @@
gameOver=Game Ose
white=Funfun
black=Dudu
whiteIsVictorious=Funfun ni ṣẹgun
blackIsVictorious=Dudu ni ṣẹgun
analysis=Onínọmbà
signUp=Forukọsilẹ
games=Ere
forum=Forum
correspondence=Lẹta
time=akoko
rating=Rating
password=ọrọigbaniwọle
changePassword=Ayipada ọrọigbaniwọle
changeEmail=Ayipada imeeli
email=Imeeli
accept=Gba awọn
decline=Sile
casual=Unrated
rated=Won won
send=Firanṣẹ
spectators=Ayé ti:
search=àwárí
tournament=Figagbaga
tournaments=Awọn ere-idije
teams=Egbe
allTeams=Gbogbo egbe
newTeam=Titun egbe
myTeams=Mi egbe
settings=Eto
apply=Waye
leaderboard=Leaderboard
more=Diẹ
winner=Olubori ọkunrin
country=Orilẹ-ede
training=Ikẹkọ
victory=Isegun!
thankYou=E dupe!

View file

@ -182,6 +182,7 @@ GET /translation/form/:lang controllers.I18n.translationForm(lang: St
POST /translation/post/:lang controllers.I18n.translationPost(lang: String)
GET /translation/fetch/:from controllers.I18n.fetch(from: Int)
POST /translation/hideCalls controllers.I18n.hideCalls
POST /translation/select controllers.I18n.select
# Authentication
GET /login controllers.Auth.login

35
issues Normal file
View file

@ -0,0 +1,35 @@
# ornicar/lila open issues
184 Desktop notifications [feature] 1
183 antichess doesn't play well with takebacks [bug] 1
181 miniboard empty [bug] [planned] 1
180 autoswitch after no other game in simul [improvement] [planned]
179 simul autoswitch on game end [improvement] [planned]
178 make sure ragequit is disabled in simul [bug] [planned]
176 Spectator chat split by move number for the benefit of kibitzing context [feature]
175 Implement realtime private messaging a la online-go/facebook [feature] 1
174 clicking on other language subdomain should retain preferred language [feature] [planned]
169 Predefined positions for game creation [feature] [planned]
168 Add advanced options in analysis board [feature]
167 Atomic chess [feature] [in progress]
166 make puzzle of the day embeddable for site owners [feature]
165 [Translation] To French. 'only accepts challenges from friends.' 1
164 Unsubscribe from something [feature]
163 The "%s games" link under favourite opponents no longer works
161 TV channels [feature]
155 Link Win/Draw/Loss indicator in Tournament Standings to Games [improvement]
153 Add support for anti-chess in PDF exporter [improvement]
149 Simul tournament [feature] [planned] @
143 Stream title on TV doesn't wrap [improvement] 2
142 Predefine move sequences in correspondence chess [feature] [planned] 3
130 Several colours for clock bar [feature]
128 when importing PGN, accept clock information [feature] 2
127 export clock information in PGN [feature] 6
126 Feature: Possibility to edit/delete forum posts [feature]
114 Wrong encoding in names of wiki-pages [bug]
97 Feature: Ability to rematch with different game settings [feature] 2
77 Feature wish: Training mode long time chart [feature]
73 Intermitent problem creating tournaments [bug] [can't reproduce]
69 timing in "Spectator room" chat [feature]
59 Feature: Clock pause [feature] 1
58 separate TVs for bullet-time, blitz and slow games. [feature] [planned] 1
53 Advanced comments [feature] 3

View file

@ -21,6 +21,8 @@ object HTTPRequest {
def userAgent(req: RequestHeader): Option[String] = req.headers get HeaderNames.USER_AGENT
def referer(req: RequestHeader): Option[String] = req.headers get HeaderNames.REFERER
def sid(req: RequestHeader): Option[String] = req.session get "sid"
private val isBotPattern = {

View file

@ -4,13 +4,17 @@ import chess.format.{ pgn => chessPgn }
object Rewind {
private def createTags(fen: Option[String], game: Game) = {
val variantTag = Some(chessPgn.Tag(_.Variant, game.variant.name))
val fenTag = fen map (fenString => chessPgn.Tag(_.FEN, fenString))
List(variantTag, fenTag).flatten
}
def apply(game: Game, initialFen: Option[String]): Valid[Progress] = chessPgn.Reader.movesWithSans(
moveStrs = game.pgnMoves,
op = sans => sans.isEmpty.fold(sans, sans.init),
tags = initialFen.??(fen => List(
chessPgn.Tag(_.FEN, fen),
chessPgn.Tag(_.Variant, game.variant.name)
))) map { replay =>
tags = createTags(initialFen, game)) map { replay =>
val rewindedGame = replay.state
val rewindedHistory = rewindedGame.board.history
val rewindedSituation = rewindedGame.situation

View file

@ -81,7 +81,8 @@ private[i18n] object Contributors {
"mg" -> List("RAZAFIMAHEFA Aina"),
"kb" -> List("skafis"),
"zu" -> List("Dragon135"),
"ur" -> List("alleey"))
"ur" -> List("alleey"),
"yo" -> List("Mikhail04"))
def apply(code: String): List[String] = ~(all get code)
}

View file

@ -13,5 +13,7 @@ case class I18nDomain(domain: String) {
lazy val commonDomain = hasLang.fold(parts drop 1 mkString ".", domain)
def withLang(lang: Lang) = I18nDomain(lang.language + "." + commonDomain)
def withLang(lang: Lang): I18nDomain = withLang(lang.language)
def withLang(lang: String): I18nDomain = I18nDomain(lang + "." + commonDomain)
}

View file

@ -11,6 +11,8 @@ private[i18n] case class I18nPool(val langs: Set[Lang], val default: Lang) {
val names: Map[String, String] = (langs map langNames).toMap
val contains: Set[String] = langs.map(_.language)
private def langNames(lang: Lang): (String, String) =
lang.language -> LangList.nameOrCode(lang.language)

View file

@ -1,8 +1,7 @@
package lila.i18n
import play.api.i18n.Lang
import play.api.mvc.Results.Redirect
import play.api.mvc.{ Action, RequestHeader, Handler }
import play.api.mvc.{ Action, RequestHeader, Handler, Result }
import lila.common.HTTPRequest
@ -12,15 +11,20 @@ final class I18nRequestHandler(
cdnDomain: String) {
def apply(req: RequestHeader): Option[Handler] =
(HTTPRequest.isRedirectable(req) &&
!pool.domainLang(req).isDefined &&
req.host != cdnDomain
) option Action {
Redirect(redirectUrl(req))
}
if (HTTPRequest.isRedirectable(req) &&
req.host != cdnDomain &&
pool.domainLang(req).isEmpty) Some(Action(Redirect(redirectUrl(req))))
else None
def forUser(req: RequestHeader, userOption: Option[lila.user.User]): Option[Result] = for {
userLang <- userOption.flatMap(_.lang)
reqLang <- pool domainLang req
if userLang != reqLang.language
} yield Redirect(redirectUrlLang(req, userLang))
private def redirectUrl(req: RequestHeader) =
protocol +
I18nDomain(req.domain).withLang(pool preferred req).domain +
req.uri
redirectUrlLang(req, pool.preferred(req).language)
private def redirectUrlLang(req: RequestHeader, lang: String) =
s"$protocol${I18nDomain(req.domain).withLang(lang).domain}${req.uri}"
}

View file

@ -58,7 +58,6 @@ final class Env(
def receive = {
case User.Active(user, lang) =>
if (!user.seenRecently) UserRepo setSeenAt user.id
if (user.lang != lang.some) UserRepo.setLang(user.id, lang)
onlineUserIdMemo put user.id
}
})), 'userActive)

View file

@ -257,9 +257,7 @@ trait UserRepo {
"count.game" -> $gt(4)
), "_id")(_.asOpt[String])
def setLang(id: ID, lang: String) {
$update.fieldUnchecked(id, "lang", lang)
}
def setLang(id: ID, lang: String) = $update.field(id, "lang", lang)
def idsSumToints(ids: Iterable[String]): Fu[Int] = ids.isEmpty ? fuccess(0) | {
import reactivemongo.core.commands._

View file

@ -904,9 +904,8 @@ lichess.storage = {
url: $links.data('url'),
success: function(list) {
$links.prepend(list.map(function(lang) {
var href = location.href.replace(/\/\/\w+\./, '//' + lang[0] + '.');
var klass = $.fp.contains(langs, lang[0]) ? 'class="accepted"' : '';
return '<li><a ' + klass + ' lang="' + lang[0] + '" href="' + href + '">' + lang[1] + '</a></li>';
return '<li><button type="submit" ' + klass + '" name="lang" value="' + lang[0] + '">' + lang[1] + '</button></li>';
}).join(''));
}
});

View file

@ -1217,42 +1217,48 @@ div.lichess_language {
float: right;
position: relative;
}
ul.language_links {
.language_links {
width: 620px;
padding: 10px;
max-height: 700px;
overflow: auto;
}
#top div.lichess_language.shown ul.language_links {
#top div.lichess_language.shown .language_links {
display: block;
}
ul.language_links a {
.language_links button {
display: block;
padding: 0.4em 0.5em;
text-decoration: none;
border: none;
background: none;
width: 100%;
text-align: left;
transition: 0.13s;
}
ul.language_links a.current,
ul.language_links a.accepted {
.language_links button.current,
.language_links button.accepted {
font-weight: bold;
color: #444;
}
ul.language_links a.accepted {
.language_links button.accepted {
text-decoration: underline;
}
ul.language_links a:hover,
ul.language_links a.current {
.language_links button:hover,
.language_links button.current {
background: #F0F0F0;
}
ul.language_links li {
.language_links li {
width: 25%;
height: 24px;
float: left;
display: block;
overflow: hidden;
}
ul.language_links li:last-child {
.language_links li:last-child {
width: 50%;
font-weight: bold;
padding-top: 10px;
}
#themepicker {
float: right;
@ -1265,7 +1271,7 @@ ul.language_links li:last-child {
text-decoration: none;
line-height: normal;
}
#themepicker.shown div.dropdown {
#themepicker.shown .dropdown {
display: block;
width: 241px;
text-align: center;
@ -1824,25 +1830,6 @@ div.lichess_language {
float: right;
position: relative;
}
ul.language_links {
width: 620px;
padding: 10px;
max-height: 700px;
overflow: auto;
}
#top div.lichess_language.shown ul.language_links {
display: block;
}
ul.language_links a {
display: block;
padding: 0.4em 0.5em;
text-decoration: none;
transition: 0.13s;
}
ul.language_links a.current,
ul.language_links a.accepted {
font-weight: bold;
}
div.undertable {
width: 512px;
font-size: 11.5px;

View file

@ -151,8 +151,7 @@ body.dark #themepicker div.theme:hover {
}
body.dark #hooks_wrap div.tabs a,
body.dark #top div.auth .links a:hover,
body.dark #top ul.language_links a:hover,
body.dark #top ul.language_links a.current,
body.dark #top .language_links button:hover,
body.dark #challenge_notifications > div.notification:hover,
body.dark #message_notifications div.notification:hover {
background-color: #3e3e3e;
@ -276,7 +275,7 @@ body.dark #friend_box,
body.dark #powerTip {
background: #343434;
}
body.dark #top ul.language_links a.accepted,
body.dark #top .language_links button.accepted,
body.dark #lichess_forum div.post .message,
body.dark #lichess_blog,
body.dark #lichess_message div.thread_message div.thread_message_body,

View file

@ -19,7 +19,7 @@ module.exports = function(ctrl, player, klass) {
},
class: 'text user_link ' + (player.user.online ? 'online is-green' : 'offline') + (klass ? ' ' + klass : ''),
href: '/@/' + player.user.username,
target: game.isPlayerPlaying(ctrl.data) ? '_blank' : null,
target: game.isPlayerPlaying(ctrl.data) ? '_blank' : '_self',
'data-icon': 'r',
}, [
(player.user.title ? player.user.title + ' ' : '') + player.user.username,

View file

@ -2,6 +2,13 @@ var m = require('mithril');
var boardContent = m('div.cg-board-wrap', m('div.cg-board'));
function timer(pov) {
var time = moment().add(pov.secondsLeft, 'seconds');
return m('time.moment-from-now', {
datetime: time
}, time.fromNow());
}
module.exports = function(ctrl) {
return m('div#now_playing',
ctrl.data.nowPlaying.map(function(pov) {
@ -15,18 +22,13 @@ module.exports = function(ctrl) {
'data-fen': pov.fen,
'data-lastmove': pov.lastMove,
config: function(el, isUpdate) {
if (isUpdate) return;
lichess.parseFen($(el));
if (!isUpdate) lichess.parseFen($(el));
}
}, boardContent),
m('span.meta', [
pov.opponent.aiLevel ? ctrl.trans('aiNameLevelAiLevel', 'Stockfish', pov.opponent.aiLevel) : pov.opponent.username,
m('span.indicator',
pov.isMyTurn ? (pov.secondsLeft ? m('time.moment-from-now', {
config: function(el, isUpdate) {
if (!isUpdate) el.setAttribute('datetime', moment().add(pov.secondsLeft, 'seconds'));
}
}) : ctrl.trans('yourTurn')) : m.trust('&nbsp;'))
pov.isMyTurn ? (pov.secondsLeft ? timer(pov) : ctrl.trans('yourTurn')) : m.trust('&nbsp;'))
])
])
})

View file

@ -34,7 +34,7 @@ module.exports = function(ctrl, key) {
}.bind(this);
this.next = function(id) {
if (!this.value || !game.isPlayerPlaying(ctrl.data) || game.isPlayerTurn(ctrl.data)) return;
if (!this.value || ctrl.data.player.spectator || game.isPlayerTurn(ctrl.data)) return;
if (id) goToId(id);
else xhr.next(ctrl).then(function(data) {
if (data.next) goToId(data.next);