translation contexts using github wiki for data storage

This commit is contained in:
Thibault Duplessis 2013-12-24 13:59:40 +01:00
parent 3974c44216
commit a6235e8a39
8 changed files with 119 additions and 42 deletions

View file

@ -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)

View file

@ -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>

View 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
}

View file

@ -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

View file

@ -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
}

View file

@ -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 =

View file

@ -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 {

View file

@ -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%;