translation contexts using github wiki for data storage
This commit is contained in:
parent
3974c44216
commit
a6235e8a39
|
@ -1,13 +1,13 @@
|
|||
package controllers
|
||||
|
||||
import lila.app._
|
||||
import views._
|
||||
import lila.user.Context
|
||||
import lila.i18n.{ Translation, TransInfo }
|
||||
import lila.common.{ Captcha, LilaCookie }
|
||||
|
||||
import play.api.data.Form
|
||||
|
||||
import lila.app._
|
||||
import lila.common.{ Captcha, LilaCookie }
|
||||
import lila.i18n.{ Translation, TransInfo }
|
||||
import lila.user.Context
|
||||
import views._
|
||||
|
||||
object I18n extends LilaController {
|
||||
|
||||
private def env = Env.i18n
|
||||
|
@ -18,29 +18,39 @@ object I18n extends LilaController {
|
|||
}
|
||||
|
||||
def translationForm(lang: String) = Open { implicit ctx ⇒
|
||||
OptionFuOk(fuccess(env.transInfos get lang)) { info ⇒
|
||||
env.forms.translationWithCaptcha map {
|
||||
case (form, captcha) ⇒ renderTranslationForm(form, info, captcha)
|
||||
OptionFuOk(infoAndContext(lang)) {
|
||||
case (info, context) ⇒ env.forms.translationWithCaptcha map {
|
||||
case (form, captcha) ⇒ renderTranslationForm(form, info, captcha, context = context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def translationPost(lang: String) = OpenBody { implicit ctx ⇒
|
||||
OptionFuResult(fuccess(env.transInfos get lang)) { info ⇒
|
||||
implicit val req = ctx.body
|
||||
val data = env.forms.decodeTranslationBody
|
||||
FormFuResult(env.forms.translation) { form ⇒
|
||||
env.forms.anyCaptcha map { captcha ⇒
|
||||
renderTranslationForm(form, info, captcha, data)
|
||||
OptionFuResult(infoAndContext(lang)) {
|
||||
case (info, context) ⇒
|
||||
implicit val req = ctx.body
|
||||
val data = env.forms.decodeTranslationBody
|
||||
FormFuResult(env.forms.translation) { form ⇒
|
||||
env.forms.anyCaptcha map { captcha ⇒
|
||||
renderTranslationForm(form, info, captcha, data = data, context = context)
|
||||
}
|
||||
} { metadata ⇒
|
||||
env.forms.process(lang, metadata, data) inject
|
||||
Redirect(routes.I18n.contribute).flashing("success" -> "1")
|
||||
}
|
||||
} { metadata ⇒
|
||||
env.forms.process(lang, metadata, data) inject
|
||||
Redirect(routes.I18n.contribute).flashing("success" -> "1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def renderTranslationForm(form: Form[_], info: TransInfo, captcha: Captcha, data: Map[String, String] = Map.empty)(implicit ctx: Context) =
|
||||
private def infoAndContext(lang: String) = env.transInfos.get(lang) ?? { i ⇒
|
||||
env.context.get map (i -> _) map (_.some)
|
||||
}
|
||||
|
||||
private def renderTranslationForm(
|
||||
form: Form[_],
|
||||
info: TransInfo,
|
||||
captcha: Captcha,
|
||||
context: Map[String, String],
|
||||
data: Map[String, String] = Map.empty)(implicit ctx: Context) =
|
||||
html.i18n.translationForm(
|
||||
info,
|
||||
form,
|
||||
|
@ -48,7 +58,8 @@ object I18n extends LilaController {
|
|||
env.pool.default,
|
||||
env.translator.rawTranslation(info.lang) _,
|
||||
captcha,
|
||||
data)
|
||||
data = data,
|
||||
context = context)
|
||||
|
||||
def fetch(from: Int) = Open { implicit ctx ⇒
|
||||
JsonOk(env jsonFromVersion from)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@(info: lila.i18n.TransInfo, form: Form[_], keys: lila.i18n.I18nKeys, baseLang: Lang, rawTrans: String => Option[String], captcha: lila.common.Captcha, data: Map[String, String])(implicit ctx: Context)
|
||||
@(info: lila.i18n.TransInfo, form: Form[_], keys: lila.i18n.I18nKeys, baseLang: Lang, rawTrans: String => Option[String], captcha: lila.common.Captcha, data: Map[String, String], context: Map[String, String])(implicit ctx: Context)
|
||||
|
||||
@goodies = {
|
||||
<br />
|
||||
|
@ -32,22 +32,27 @@ No need to submit a complete translation. You can just translate some sentences,
|
|||
<div class="messages">
|
||||
@keys.keys.zipWithIndex.map {
|
||||
case (key, i) => {
|
||||
@defining(key.to(baseLang)()) { baseKey =>
|
||||
<div class="field message">
|
||||
<span class="number">@{i+1}/@info.nbMessages</span>
|
||||
<label for="key_@key.key">@key.to(baseLang)()</label>
|
||||
<label for="key_@key.key">@baseKey</label>
|
||||
<input
|
||||
@transValidationPattern(key.to(baseLang)()).map { pattern =>
|
||||
@transValidationPattern(baseKey).map { pattern =>
|
||||
pattern="@pattern"
|
||||
}
|
||||
type="text"
|
||||
name="key_@key.key"
|
||||
id="key_@key.key"
|
||||
value="@data.get(key.key).getOrElse(rawTrans(key.key))" />
|
||||
@context.get(baseKey).map { c =>
|
||||
<p class="context">@c</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<p>These fields are optional:</p>
|
||||
<p>The following fields are optional, and not part of the translation:</p>
|
||||
<br />
|
||||
<div class="field optional">
|
||||
<label for="@form("author").id">Author</label>
|
||||
|
|
56
modules/i18n/src/main/Context.scala
Normal file
56
modules/i18n/src/main/Context.scala
Normal file
|
@ -0,0 +1,56 @@
|
|||
package lila.i18n
|
||||
|
||||
import java.io.File
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.Future
|
||||
|
||||
import com.google.common.io.Files
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.Repository
|
||||
import org.eclipse.jgit.storage.file.FileRepository
|
||||
|
||||
import lila.memo.{ AsyncCache }
|
||||
|
||||
private[i18n] final class Context(gitUrl: String, gitFile: String, keys: I18nKeys) {
|
||||
|
||||
type Contexts = Map[String, String]
|
||||
|
||||
def get: Fu[Contexts] = cache(true)
|
||||
|
||||
private val cache = AsyncCache[Boolean, Contexts]((_: Boolean) ⇒ fetch, timeToLive = 1 hour)
|
||||
|
||||
private def parse(text: String): Contexts =
|
||||
text.lines.toList.map(_.trim).filter(_.nonEmpty).map(_.split('=')).foldLeft(Map[String, String]()) {
|
||||
case (cs, Array(key, text)) if (keySet contains key) ⇒ cs + (key -> text)
|
||||
case (cs, Array(key, _)) ⇒ {
|
||||
logwarn("i18n context skipped key " + key)
|
||||
cs
|
||||
}
|
||||
case (cs, line) ⇒ {
|
||||
logwarn("i18n context skipped line " + line.mkString("="))
|
||||
cs
|
||||
}
|
||||
}
|
||||
|
||||
private lazy val keySet: Set[String] = keys.keys.map(_.en()).toSet
|
||||
|
||||
private def fetch: Fu[Contexts] = gitClone map { dir ⇒
|
||||
val filePath = s"${dir.getAbsolutePath}/$gitFile"
|
||||
val content = fileContent(new File(filePath))
|
||||
parse(content)
|
||||
}
|
||||
|
||||
private def gitClone: Fu[File] = Future {
|
||||
val dir = Files.createTempDir
|
||||
dir.deleteOnExit
|
||||
Git.cloneRepository
|
||||
.setURI(gitUrl)
|
||||
.setDirectory(dir)
|
||||
.setBare(false)
|
||||
.call
|
||||
dir
|
||||
}
|
||||
|
||||
private def fileContent(file: File) =
|
||||
scala.io.Source.fromFile(file.getCanonicalPath).mkString
|
||||
}
|
|
@ -20,6 +20,8 @@ final class Env(
|
|||
val HideCallsCookieName = config getString "hide_calls.cookie.name"
|
||||
val HideCallsCookieMaxAge = config getInt "hide_calls.cookie.max_age"
|
||||
val CollectionTranslation = config getString "collection.translation"
|
||||
val ContextGitUrl = config getString "context.git.url"
|
||||
val ContextGitFile = config getString "context.git.file"
|
||||
}
|
||||
import settings._
|
||||
|
||||
|
@ -30,7 +32,7 @@ final class Env(
|
|||
|
||||
lazy val pool = new I18nPool(
|
||||
langs = Lang.availables(play.api.Play.current).toSet,
|
||||
default = Lang("en"))
|
||||
default = I18nKey.en)
|
||||
|
||||
lazy val translator = new Translator(
|
||||
api = messagesApi,
|
||||
|
@ -66,6 +68,8 @@ final class Env(
|
|||
repoPath = appPath,
|
||||
system = system)
|
||||
|
||||
lazy val context = new Context(ContextGitUrl, ContextGitFile, keys)
|
||||
|
||||
def hideCallsCookieName = HideCallsCookieName
|
||||
def hideCallsCookieMaxAge = HideCallsCookieMaxAge
|
||||
|
||||
|
|
|
@ -3,26 +3,24 @@ package lila.i18n
|
|||
import play.api.i18n.Lang
|
||||
import play.api.templates.Html
|
||||
|
||||
import lila.user.Context
|
||||
|
||||
trait I18nKey {
|
||||
|
||||
val key: String
|
||||
|
||||
def apply(args: Any*)(implicit ctx: Context): Html
|
||||
def apply(args: Any*)(implicit ctx: lila.user.Context): Html
|
||||
|
||||
def str(args: Any*)(implicit ctx: Context): String
|
||||
def str(args: Any*)(implicit ctx: lila.user.Context): String
|
||||
|
||||
def to(lang: Lang)(args: Any*): String
|
||||
|
||||
def en(args: Any*): String = to(I18nKey.en)(args)
|
||||
def en(args: Any*): String = to(I18nKey.en)(args:_ *)
|
||||
}
|
||||
|
||||
case class Untranslated(key: String) extends I18nKey {
|
||||
|
||||
def apply(args: Any*)(implicit ctx: Context) = Html(key)
|
||||
def apply(args: Any*)(implicit ctx: lila.user.Context) = Html(key)
|
||||
|
||||
def str(args: Any*)(implicit ctx: Context) = key
|
||||
def str(args: Any*)(implicit ctx: lila.user.Context) = key
|
||||
|
||||
def to(lang: Lang)(args: Any*) = key
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// Generated with bin/trans-dump
|
||||
package lila.i18n
|
||||
|
||||
import lila.user.Context
|
||||
|
||||
import play.api.templates.Html
|
||||
import play.api.i18n.Lang
|
||||
|
||||
|
@ -10,10 +8,10 @@ final class I18nKeys(translator: Translator) {
|
|||
|
||||
final class Key(val key: String) extends I18nKey {
|
||||
|
||||
def apply(args: Any*)(implicit ctx: Context): Html =
|
||||
def apply(args: Any*)(implicit ctx: lila.user.Context): Html =
|
||||
translator.html(key, args.toList)(ctx.req)
|
||||
|
||||
def str(args: Any*)(implicit ctx: Context): String =
|
||||
def str(args: Any*)(implicit ctx: lila.user.Context): String =
|
||||
translator.str(key, args.toList)(ctx.req)
|
||||
|
||||
def to(lang: Lang)(args: Any*): String =
|
||||
|
|
|
@ -21,18 +21,18 @@ private[i18n] final class Translator(api: MessagesApi, pool: I18nPool) {
|
|||
def rawTranslation(lang: Lang)(key: String): Option[String] =
|
||||
messages get lang.code flatMap (_ get key)
|
||||
|
||||
def defaultTranslation(key: String, args: List[Any]): Option[String] =
|
||||
private def defaultTranslation(key: String, args: List[Any]): Option[String] =
|
||||
defaultMessages get key flatMap { pattern ⇒
|
||||
formatTranslation(key, pattern, args)
|
||||
formatTranslation(key, pattern, args)
|
||||
}
|
||||
|
||||
private def translate(key: String, args: List[Any])(lang: Lang): Option[String] =
|
||||
if (lang == pool.default) defaultTranslation(key, args)
|
||||
if (lang.language == pool.default.language) defaultTranslation(key, args)
|
||||
else messages get lang.code flatMap (_ get key) flatMap { pattern ⇒
|
||||
formatTranslation(key, pattern, args)
|
||||
formatTranslation(key, pattern, args)
|
||||
} orElse defaultTranslation(key, args)
|
||||
|
||||
def formatTranslation(key: String, pattern: String, args: List[Any]) = try {
|
||||
private def formatTranslation(key: String, pattern: String, args: List[Any]) = try {
|
||||
Some(if (args.isEmpty) pattern else pattern.format(args: _*))
|
||||
}
|
||||
catch {
|
||||
|
|
|
@ -75,6 +75,11 @@ div.message label {
|
|||
color: #444;
|
||||
margin-left: 9%;
|
||||
}
|
||||
div.message .context {
|
||||
margin-left: 9%;
|
||||
font-size: 0.9em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
div.message input {
|
||||
width: 90%;
|
||||
margin-left: 9%;
|
||||
|
|
Loading…
Reference in a new issue