lila/project/MessageCompiler.scala

158 lines
4.7 KiB
Scala
Raw Normal View History

2016-03-29 00:29:32 -06:00
import _root_.java.io.File
import sbt._, Keys._
import scala.io.Source
2017-05-27 03:02:14 -06:00
import scala.xml.XML
2016-03-29 00:29:32 -06:00
object MessageCompiler {
def apply(sourceDir: File, destDir: File, dbs: List[String], compileTo: File): Seq[File] =
dbs.flatMap { db =>
doFile(
db = db,
sourceFile = sourceDir / s"$db.xml",
destDir = destDir / db,
2017-08-23 17:39:19 -06:00
compileTo = compileTo / db
)
}
2017-07-06 07:22:23 -06:00
private def doFile(db: String, sourceFile: File, destDir: File, compileTo: File): Seq[File] = {
2017-07-15 08:42:53 -06:00
destDir.mkdirs()
2019-12-12 22:03:38 -07:00
val registry = ("en-GB" -> sourceFile) :: destDir.list.toList
.map { f =>
f.takeWhile('.' !=) -> (destDir / f)
}
.sortBy(_._1)
2017-05-25 07:11:42 -06:00
compileTo.mkdirs()
var translatedLocales = Set.empty[String]
val res = for {
entry <- registry
compilable <- {
val (locale, file) = entry
2019-12-12 22:03:38 -07:00
val compileToFile = compileTo / s"$locale.scala"
if (!isFileEmpty(file)) {
translatedLocales = translatedLocales + locale
if (file.lastModified > compileToFile.lastModified) {
printToFile(compileToFile)(render(db, locale, file))
}
Some(compileToFile)
2019-12-12 22:03:38 -07:00
} else None
}
} yield compilable
writeRegistry(db, compileTo, translatedLocales) :: res
}
2018-04-09 17:07:19 -06:00
private def isFileEmpty(f: File) = {
Source.fromFile(f, "UTF-8").getLines.drop(2).next == "<resources></resources>"
}
2020-02-07 13:21:41 -07:00
private def packageName(db: String) = if (db == "class") "clas" else db
private def writeRegistry(db: String, compileTo: File, locales: Iterable[String]) = {
2017-05-25 07:11:42 -06:00
val file = compileTo / "Registry.scala"
printToFile(file) {
val content = locales.map { locale =>
s"""Lang("${locale.replace("-", "\",\"")}")->`$locale`.load"""
} mkString ",\n"
s"""package lila.i18n
2020-02-07 13:21:41 -07:00
package db.${packageName(db)}
2017-05-26 06:12:04 -06:00
import play.api.i18n.Lang
2017-02-14 08:19:01 -07:00
// format: OFF
2017-05-27 05:25:24 -06:00
private[i18n] object Registry {
def load = Map[Lang, java.util.HashMap[MessageKey, Translation]]($content)
}
"""
}
file
}
2017-05-27 03:02:14 -06:00
private def ucfirst(str: String) = str(0).toUpper + str.drop(1)
private def toKey(e: scala.xml.Node) = s""""${e.\("@name")}""""
2017-05-27 15:02:49 -06:00
private def escape(str: String) = {
// is someone trying to inject scala code?
if (str contains "\"\"\"") sys error s"Skipped translation: $str"
2017-05-27 15:02:49 -06:00
// crowdin escapes ' and " with \, and encodes &. We'll do it at runtime instead.
else str.replace("\\'", "'").replace("\\\"", "\"")
}
private def render(db: String, locale: String, file: File): String = {
2019-12-12 22:03:38 -07:00
val xml =
try {
XML.loadFile(file)
} catch {
case e: Exception => println(file); throw e;
}
def quote(msg: String) = s"""""\"$msg""\""""
2017-05-27 03:02:14 -06:00
val content = xml.child.collect {
case e if e.label == "string" =>
val safe = escape(e.text)
2018-03-25 21:28:55 -06:00
val translation = escapeHtmlOption(safe) match {
2019-12-12 22:03:38 -07:00
case None => s"""new Simple(\"\"\"$safe\"\"\")"""
2018-03-25 21:28:55 -06:00
case Some(escaped) => s"""new Escaped(\"\"\"$safe\"\"\",\"\"\"$escaped\"\"\")"""
}
s"""m.put(${toKey(e)},$translation)"""
2017-05-27 03:02:14 -06:00
case e if e.label == "plurals" =>
2019-12-12 22:03:38 -07:00
val items: Map[String, String] = e.child
.filter(_.label == "item")
.map { i =>
ucfirst(i.\("@quantity").toString) -> s"""\"\"\"${escape(i.text)}\"\"\""""
}
.toMap
s"""m.put(${toKey(e)},new Plurals(${pluralMap(items)}))"""
2017-05-27 03:02:14 -06:00
}
s"""package lila.i18n
2020-02-07 13:21:41 -07:00
package db.${packageName(db)}
2017-05-27 03:02:14 -06:00
import I18nQuantity._
2017-02-14 08:19:01 -07:00
// format: OFF
private object `$locale` {
def load: java.util.HashMap[MessageKey, Translation] = {
2019-12-05 15:40:45 -07:00
val m = new java.util.HashMap[MessageKey, Translation](${content.size + 1})
${content mkString "\n"}
m
}
}
"""
}
private def pluralMap(items: Map[String, String]): String =
2019-12-12 22:03:38 -07:00
if (items.size > 4) s"""Map(${items.map { case (k, v) => s"$k->$v" } mkString ","})"""
else s"""new Map.Map${items.size}(${items.map { case (k, v) => s"$k,$v" } mkString ","})"""
2018-06-25 21:33:20 -06:00
private val badChars = """[<>&"'\r\n]""".r.pattern
private def escapeHtmlOption(s: String): Option[String] =
if (badChars.matcher(s).find) Some {
2018-06-25 21:33:20 -06:00
val sb = new java.lang.StringBuilder(s.size + 10) // wet finger style
2019-12-12 22:03:38 -07:00
var i = 0
while (i < s.length) {
s.charAt(i) match {
case '<' => sb append "&lt;"
case '>' => sb append "&gt;"
case '&' => sb append "&amp;"
case '"' => sb append "&quot;"
case '\'' => sb append "&#39;"
case '\r' => ()
case '\n' => sb append "<br />"
case c => sb append c
}
i += 1
}
2018-06-25 21:33:20 -06:00
sb.toString
} else None
private def printToFile(f: File)(content: String): Unit = {
2017-03-21 08:33:42 -06:00
val p = new java.io.PrintWriter(f, "UTF-8")
2019-12-12 22:03:38 -07:00
try {
content.foreach(p.print)
} finally {
p.close()
}
2016-03-29 00:29:32 -06:00
}
}