diff --git a/bin/trans-dump b/bin/trans-dump
index 1a1918b786a68..1c034edac568e 100755
--- a/bin/trans-dump
+++ b/bin/trans-dump
@@ -7,7 +7,7 @@ const path = require('path');
const lilaDir = path.resolve(__dirname, '..');
const baseDir = path.resolve(lilaDir, 'translation/source');
const dbs =
- 'site arena emails learn activity coordinates study clas contact appeal patron coach broadcast streamer tfa settings preferences team perfStat search tourname faq lag swiss puzzle puzzleTheme challenge storm ublog insight keyboardMove timeago oauthScope dgt voiceCommands onboarding'.split(
+ 'site arena emails learn activity coordinates study clas contact appeal patron coach broadcast streamer tfa settings preferences team perfStat search tourname faq lag swiss puzzle puzzleTheme challenge storm ublog insight keyboardMove timeago oauthScope dgt voiceCommands onboarding features'.split(
' ',
);
diff --git a/build.sbt b/build.sbt
index 04b80bdecdecb..86718134fa9d0 100644
--- a/build.sbt
+++ b/build.sbt
@@ -124,7 +124,7 @@ lazy val i18n = module("i18n",
I18n.serialize(
sourceDir = new File("translation/source"),
destDir = new File("translation/dest"),
- dbs = "site arena emails learn activity coordinates study class contact appeal patron coach broadcast streamer tfa settings preferences team perfStat search tourname faq lag swiss puzzle puzzleTheme challenge storm ublog insight keyboardMove timeago oauthScope dgt voiceCommands onboarding".split(' ').toList,
+ dbs = "site arena emails learn activity coordinates study class contact appeal patron coach broadcast streamer tfa settings preferences team perfStat search tourname faq lag swiss puzzle puzzleTheme challenge storm ublog insight keyboardMove timeago oauthScope dgt voiceCommands onboarding features".split(' ').toList,
outputFile
)
}.taskValue
diff --git a/modules/coreI18n/src/main/key.scala b/modules/coreI18n/src/main/key.scala
index 6f04bc38d9e2d..44ec4375b3b60 100644
--- a/modules/coreI18n/src/main/key.scala
+++ b/modules/coreI18n/src/main/key.scala
@@ -164,6 +164,7 @@ object I18nKey:
val `latestForumPosts`: I18nKey = "latestForumPosts"
val `players`: I18nKey = "players"
val `friends`: I18nKey = "friends"
+ val `otherPlayers`: I18nKey = "otherPlayers"
val `discussions`: I18nKey = "discussions"
val `today`: I18nKey = "today"
val `yesterday`: I18nKey = "yesterday"
@@ -421,6 +422,8 @@ object I18nKey:
val `descPrivateHelp`: I18nKey = "descPrivateHelp"
val `no`: I18nKey = "no"
val `yes`: I18nKey = "yes"
+ val `website`: I18nKey = "website"
+ val `mobile`: I18nKey = "mobile"
val `help`: I18nKey = "help"
val `createANewTopic`: I18nKey = "createANewTopic"
val `topics`: I18nKey = "topics"
@@ -438,7 +441,6 @@ object I18nKey:
val `whatIsIheMatter`: I18nKey = "whatIsIheMatter"
val `cheat`: I18nKey = "cheat"
val `troll`: I18nKey = "troll"
- val `ratingManipulation`: I18nKey = "ratingManipulation"
val `other`: I18nKey = "other"
val `reportDescriptionHelp`: I18nKey = "reportDescriptionHelp"
val `error.provideOneCheatedGameLink`: I18nKey = "error.provideOneCheatedGameLink"
@@ -1497,9 +1499,7 @@ object I18nKey:
val `botRatingAbuse`: I18nKey = "contact:botRatingAbuse"
val `errorPage`: I18nKey = "contact:errorPage"
val `reportErrorPage`: I18nKey = "contact:reportErrorPage"
- val `wantBroadcastTournament`: I18nKey = "contact:wantBroadcastTournament"
val `learnHowToMakeBroadcasts`: I18nKey = "contact:learnHowToMakeBroadcasts"
- val `contactAboutOfficialBroadcasts`: I18nKey = "contact:contactAboutOfficialBroadcasts"
val `banAppeal`: I18nKey = "contact:banAppeal"
val `engineAppeal`: I18nKey = "contact:engineAppeal"
val `sendAppealTo`: I18nKey = "contact:sendAppealTo"
@@ -2736,3 +2736,34 @@ object I18nKey:
val `configureLichess`: I18nKey = "onboarding:configureLichess"
val `exploreTheSiteAndHaveFun`: I18nKey = "onboarding:exploreTheSiteAndHaveFun"
+ object features:
+ val `zeroAdsAndNoTracking`: I18nKey = "features:zeroAdsAndNoTracking"
+ val `correspondenceWithConditionalPremoves`: I18nKey = "features:correspondenceWithConditionalPremoves"
+ val `standardChessAndX`: I18nKey = "features:standardChessAndX"
+ val `deepXServerAnalysis`: I18nKey = "features:deepXServerAnalysis"
+ val `boardEditorAndAnalysisBoardWithEngine`: I18nKey = "features:boardEditorAndAnalysisBoardWithEngine"
+ val `cloudEngineAnalysis`: I18nKey = "features:cloudEngineAnalysis"
+ val `studies`: I18nKey = "features:studies"
+ val `chessInsights`: I18nKey = "features:chessInsights"
+ val `allChessBasicsLessons`: I18nKey = "features:allChessBasicsLessons"
+ val `tacticalPuzzlesFromUserGames`: I18nKey = "features:tacticalPuzzlesFromUserGames"
+ val `personalOpeningExplorerX`: I18nKey = "features:personalOpeningExplorerX"
+ val `personalOpeningExplorer`: I18nKey = "features:personalOpeningExplorer"
+ val `endgameTablebase`: I18nKey = "features:endgameTablebase"
+ val `downloadOrUploadAnyGameAsPgn`: I18nKey = "features:downloadOrUploadAnyGameAsPgn"
+ val `xThroughLichessBillionGames`: I18nKey = "features:xThroughLichessBillionGames"
+ val `tvForumBlogTeamsMessagingFriendsChallenges`: I18nKey = "features:tvForumBlogTeamsMessagingFriendsChallenges"
+ val `lightOrDarkThemeCustomBoardsPiecesAndBackground`: I18nKey = "features:lightOrDarkThemeCustomBoardsPiecesAndBackground"
+ val `allFeaturesToCome`: I18nKey = "features:allFeaturesToCome"
+ val `landscapeSupportOnApp`: I18nKey = "features:landscapeSupportOnApp"
+ val `supportLichess`: I18nKey = "features:supportLichess"
+ val `contributeToLichessAndGetIcon`: I18nKey = "features:contributeToLichessAndGetIcon"
+ val `ifYouLoveLichess`: I18nKey = "features:ifYouLoveLichess"
+ val `supportUsWithAPatronAccount`: I18nKey = "features:supportUsWithAPatronAccount"
+ val `allFeaturesAreFreeForEverybody`: I18nKey = "features:allFeaturesAreFreeForEverybody"
+ val `weBelieveEveryChessPlayerDeservesTheBest`: I18nKey = "features:weBelieveEveryChessPlayerDeservesTheBest"
+ val `ultraBulletBulletBlitzRapidClassicalAndCorrespondenceChess`: I18nKey = "features:ultraBulletBulletBlitzRapidClassicalAndCorrespondenceChess"
+ val `everybodyGetsAllFeaturesForFree`: I18nKey = "features:everybodyGetsAllFeaturesForFree"
+ val `gamesPerDay`: I18nKey = "features:gamesPerDay"
+ val `globalOpeningExplorerInNbGames`: I18nKey = "features:globalOpeningExplorerInNbGames"
+
diff --git a/modules/plan/src/main/ui/PlanPages.scala b/modules/plan/src/main/ui/PlanPages.scala
index 29455e674fcee..8bae84794ef3c 100644
--- a/modules/plan/src/main/ui/PlanPages.scala
+++ b/modules/plan/src/main/ui/PlanPages.scala
@@ -10,11 +10,12 @@ final class PlanPages(helpers: Helpers)(fishnetPerDay: Int):
def features(using Context) =
def header(name: Frag)(using Translate) = thead(
- st.tr(th(name), th(trans.patron.freeAccount()), th(trans.patron.lichessPatron()))
+ st.tr(th(name), th(trp.freeAccount()), th(trp.lichessPatron()))
)
- val unlimited = span(dataIcon := Icon.Checkmark, cls := "is is-green text unlimited")("Unlimited")
- val check = span(dataIcon := Icon.Checkmark, cls := "is is-green text check")("Yes")
- def custom(str: String) = span(dataIcon := Icon.Checkmark, cls := "is is-green text check")(str)
+ val unlimited =
+ span(dataIcon := Icon.Checkmark, cls := "is is-green text unlimited")(trans.site.unlimited())
+ val custom = span(dataIcon := Icon.Checkmark, cls := "is is-green text check")
+ val check = custom(trans.site.yes())
def all(content: Frag) = frag(td(content), td(content))
def tr(value: Frag)(text: Frag*) = st.tr(th(text), all(value))
val title = "Lichess features"
@@ -27,62 +28,57 @@ final class PlanPages(helpers: Helpers)(fishnetPerDay: Int):
):
main(cls := "box box-pad features")(
table(
- header(h1(dataIcon := Icon.ScreenDesktop)("Website")),
+ header(h1(dataIcon := Icon.ScreenDesktop)(trans.site.website())),
tbody(
tr(check)(
- strong("Zero ads")
+ strong(trans.features.zeroAdsAndNoTracking())
),
- tr(check)(
- strong("No tracking")
+ tr(unlimited)(
+ a(href := routes.Tournament.home)(trans.arena.arenaTournaments())
),
tr(unlimited)(
- "Play and create ",
- a(href := routes.Tournament.home)("tournaments")
+ a(href := routes.Swiss.home)(trans.swiss.swissTournaments())
),
tr(unlimited)(
- "Play and create ",
- a(href := routes.Simul.home)("simultaneous exhibitions")
+ a(href := routes.Simul.home)(trans.site.simultaneousExhibitions())
),
tr(unlimited)(
- "Correspondence chess with conditional premoves"
+ trans.features.correspondenceWithConditionalPremoves()
),
tr(check)(
- "Standard chess and ",
- a(href := routes.Cms.variantHome)("8 chess variants (Crazyhouse, Chess960, Horde, ...)")
+ trans.features.standardChessAndX(a(href := routes.Cms.variantHome)(trans.faq.eightVariants()))
),
- tr(custom(s"$fishnetPerDay per day"))(
- "Deep ",
- lila.ui.bits.engineFullName,
- " server analysis"
+ tr(custom(trans.features.gamesPerDay.pluralSame(fishnetPerDay)))(
+ trans.features.deepXServerAnalysis(lila.ui.bits.engineFullName)
),
tr(unlimited)(
- "Instant local Stockfish 14+ analysis (depth 99)"
+ trans.features.boardEditorAndAnalysisBoardWithEngine("Stockfish 16+ NNUE")
),
tr(unlimited)(
a(href := "https://lichess.org/blog/WN-gLzAAAKlI89Xn/thousands-of-stockfish-analysers")(
- "Cloud engine analysis"
+ trans.features.cloudEngineAnalysis()
)
),
tr(unlimited)(
a(href := "https://lichess.org/blog/WFvLpiQAACMA8e9D/learn-from-your-mistakes")(
- "Learn from your mistakes"
+ trans.site.learnFromYourMistakes()
)
),
tr(unlimited)(
a(href := "https://lichess.org/blog/V0KrLSkAAMo3hsi4/study-chess-the-lichess-way")(
- "Studies (shared and persistent analysis)"
+ trans.features.studies()
)
),
tr(unlimited)(
a(href := "https://lichess.org/blog/VmZbaigAABACtXQC/chess-insights")(
- "Chess insights (detailed analysis of your play)"
+ trans.features.chessInsights()
)
),
tr(check)(
- a(href := routes.Learn.index)("All chess basics lessons")
+ a(href := routes.Learn.index)(trans.features.allChessBasicsLessons())
),
tr(unlimited)(
- a(href := routes.Puzzle.home)("Tactical puzzles from user games")
+ a(href := routes.Puzzle.home)(trans.features.tacticalPuzzlesFromUserGames())
),
tr(unlimited)(
a(href := routes.Puzzle.streak)("Puzzle Streak"),
@@ -92,87 +88,78 @@ final class PlanPages(helpers: Helpers)(fishnetPerDay: Int):
a(href := routes.Racer.home)("Puzzle Racer")
),
tr(check)(
- a(href := s"${routes.UserAnalysis.index}#explorer")("Global opening explorer"),
- " (430 million games!)"
+ a(href := s"${routes.UserAnalysis.index}#explorer")(
+ trans.features.globalOpeningExplorerInNbGames(6_000_000_000L.localize)
+ )
),
tr(check)(
- a(href := s"${routes.UserAnalysis.index}#explorer/me")("Personal opening explorer"),
- " (also works on ",
- a(href := s"${routes.UserAnalysis.index}#explorer/DrNykterstein")("other players"),
- ")"
+ trans.features.personalOpeningExplorerX(
+ a(href := s"${routes.UserAnalysis.index}#explorer/me")(
+ trans.features.personalOpeningExplorer()
+ ),
+ a(href := s"${routes.UserAnalysis.index}#explorer/DrNykterstein")(trans.site.otherPlayers())
+ )
),
tr(unlimited)(
a(href := s"${routes.UserAnalysis.parseArg("QN4n1/6r1/3k4/8/b2K4/8/8/8_b_-_-")}#explorer")(
- "7-piece endgame tablebase"
+ trans.features.endgameTablebase()
)
),
tr(check)(
- "Download/Upload any game as PGN"
- ),
- tr(unlimited)(
- a(href := routes.Search.index(1))("Advanced search"),
- " through Lichess 4 billion games"
- ),
- tr(unlimited)(
- a(href := routes.Video.index)("Chess video library")
+ trans.features.downloadOrUploadAnyGameAsPgn()
),
tr(check)(
- "Forum, teams, messaging, friends, challenges"
+ trans.features.tvForumBlogTeamsMessagingFriendsChallenges()
),
tr(check)(
- "Available in ",
- a(href := "https://crowdin.com/project/lichess")("80+ languages")
+ trans.site.availableInNbLanguages(a(href := "https://crowdin.com/project/lichess")("140+"))
),
tr(check)(
- "Light/dark theme, custom boards, pieces and background"
+ trans.features.lightOrDarkThemeCustomBoardsPiecesAndBackground()
),
tr(check)(
- strong("All features to come, forever")
+ strong(trans.features.allFeaturesToCome())
)
),
- header(h1(dataIcon := Icon.PhoneMobile)("Mobile")),
+ header(h1(dataIcon := Icon.PhoneMobile)(trans.site.mobile())),
tbody(
tr(check)(
- strong("Zero ads, no tracking")
+ strong(trans.features.zeroAdsAndNoTracking())
),
tr(unlimited)(
- "Online and offline games, with 8 variants"
+ trans.site.onlineAndOfflinePlay()
),
tr(unlimited)(
- "Bullet, Blitz, Rapid, Classical and Correspondence chess"
+ trans.features.ultraBulletBulletBlitzRapidClassicalAndCorrespondenceChess()
),
tr(unlimited)(
- a(href := routes.Tournament.home)("Arena tournaments")
+ a(href := routes.Tournament.home)(trans.arena.arenaTournaments())
),
tr(check)(
- "Board editor and analysis board with Stockfish 14+"
+ trans.features.boardEditorAndAnalysisBoardWithEngine("Stockfish 14+")
),
tr(unlimited)(
- a(href := routes.Puzzle.home)("Tactics puzzles")
+ a(href := routes.Puzzle.home)(trans.features.tacticalPuzzlesFromUserGames())
),
tr(check)(
- "Available in 80+ languages"
+ trans.site.availableInNbLanguages(a(href := "https://crowdin.com/project/lichess")("100+"))
),
tr(check)(
- "Light and dark theme, custom boards and pieces"
+ trans.features.lightOrDarkThemeCustomBoardsPiecesAndBackground()
),
tr(check)(
- "iPhone & Android phones and tablets, landscape support"
+ trans.features.landscapeSupportOnApp()
),
tr(check)(
- strong("All features to come, forever")
+ strong(trans.features.allFeaturesToCome())
)
),
- header(h1("Support Lichess")),
+ header(h1(trans.features.supportLichess())),
tbody(cls := "support")(
st.tr(
- th(
- "Contribute to Lichess and",
- br,
- "get a cool looking Patron icon"
- ),
+ th(trans.features.contributeToLichessAndGetIcon()),
td("-"),
- td(span(dataIcon := patronIconChar, cls := "is is-green text check")("Yes"))
+ td(span(dataIcon := patronIconChar, cls := "is is-green text check")(trans.site.yes()))
),
st.tr(cls := "price")(
th,
@@ -182,17 +169,15 @@ final class PlanPages(helpers: Helpers)(fishnetPerDay: Int):
)
),
p(cls := "explanation")(
- strong("Yes, both accounts have the same features!"),
- br,
- "That is because Lichess is built for the love of chess.",
+ strong(trans.features.everybodyGetsAllFeaturesForFree()),
br,
- "We believe every chess player deserves the best, and so:",
+ trans.features.weBelieveEveryChessPlayerDeservesTheBest(),
br,
br,
- strong("all features are free for everybody, forever!"),
+ strong(trans.features.allFeaturesAreFreeForEverybody()),
br,
- "If you love Lichess, ",
- a(cls := "button", href := routes.Plan.index)("Support us with a Patron account!")
+ trans.features.ifYouLoveLichess(),
+ a(cls := "button", href := routes.Plan.index)(trans.features.supportUsWithAPatronAccount())
)
)
diff --git a/translation/source/features.xml b/translation/source/features.xml
new file mode 100644
index 0000000000000..075f80eac68bd
--- /dev/null
+++ b/translation/source/features.xml
@@ -0,0 +1,34 @@
+
+
+ Zero advertisement, no tracking
+ Correspondence chess with conditional premoves
+ Standard chess and %s
+ Deep %s server analysis
+
+ - %s game per day
+ - %s games per day
+
+ Board editor and analysis board with %s
+ Cloud engine analysis
+ Studies (shared and persistent analysis)
+ Chess insights (detailed analysis of your play)
+ All chess basics lessons
+ Tactical puzzles from user games
+ %1$s (also works on %2$s)
+ Personal opening explorer
+ Global opening explorer (%s games!)
+ 7-piece endgame tablebase
+ Download/Upload any game as PGN
+ Blog, forum, teams, TV, messaging, friends, challenges
+ Light/Dark theme, custom boards, pieces and background
+ UltraBullet, Bullet, Blitz, Rapid, Classical, Correspondence Chess
+ All features to come, forever!
+ iPhone & Android phones and tablets, landscape support
+ Support Lichess
+ Contribute to Lichess and get a cool looking Patron icon
+ Yes, both accounts have the same features!
+ We believe every chess player deserves the best, and so:
+ All features are free for everybody, forever!
+ If you love Lichess,
+ Support us with a Patron account!
+
diff --git a/translation/source/site.xml b/translation/source/site.xml
index 17beb5a640b3a..f2f57c2e32c33 100644
--- a/translation/source/site.xml
+++ b/translation/source/site.xml
@@ -180,6 +180,7 @@
Latest forum posts
Players
Friends
+ other players
Conversations
Today
Yesterday
@@ -537,6 +538,8 @@
Text that only the team members will see. If set, replaces the public description for team members.
No
Yes
+ Website
+ Mobile
Help:
Create a new topic
Topics