All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
scala.Xgettext.scala Maven / Gradle / Ivy
package scala
import java.io.{BufferedWriter, File, FileWriter}
import scala.collection.mutable.{HashMap => MHashMap, MultiMap, Set => MSet}
import scala.tools.nsc
import nsc.Global
import nsc.Phase
import nsc.plugins.Plugin
import nsc.plugins.PluginComponent
// http://www.scala-lang.org/node/140
class Xgettext(val global: Global) extends Plugin {
import global._
type i18nKey = (
Option[String], // msgctxt
String, // msgid
Option[String] // msgid_plural
)
type i18nValue = (
String, // source
Int // line
)
type i18nValues = MSet[i18nValue]
val name = "xgettext"
val description = "This Scala compiler plugin extracts and creates gettext.pot file"
val components = List[PluginComponent](MapComponent, ReduceComponent)
val OUTPUT_FILE = "i18n.pot"
val HEADER = """msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: Your Name \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"""
// -P:xgettext:[,t=xxx,tn=xxx,tc=xxx,tcn=xxx]
var i18n_class = ""
var i18n_t = Seq.empty[String]
var i18n_tn = Seq.empty[String]
var i18n_tc = Seq.empty[String]
var i18n_tcn = Seq.empty[String]
val outputFile = new File(OUTPUT_FILE)
val emptyOutputFileExists = outputFile.exists && outputFile.isFile && outputFile.length == 0
val msgToLines = new MHashMap[i18nKey, i18nValues] with MultiMap[i18nKey, i18nValue]
// Avoid running ReduceComponent multiple times
var reduced = false
override def processOptions(options: List[String], error: String => Unit) {
for (option <- options) {
if (option.startsWith("t:"))
i18n_t +:= option.stripPrefix("t:")
else if (option.startsWith("tn:"))
i18n_tn +:= option.stripPrefix("tn:")
else if (option.startsWith("tc:"))
i18n_tc +:= option.stripPrefix("tc:")
else if (option.startsWith("tcn:"))
i18n_tcn +:= option.stripPrefix("tcn:")
else
i18n_class = option
}
if (i18n_t.isEmpty) i18n_t = Seq("t")
if (i18n_tn.isEmpty) i18n_tn = Seq("tn")
if (i18n_tc.isEmpty) i18n_tc = Seq("tc")
if (i18n_tcn.isEmpty) i18n_tcn = Seq("tcn")
}
private object MapComponent extends PluginComponent {
val global: Xgettext.this.global.type = Xgettext.this.global
val runsAfter = List("refchecks")
val phaseName = "xgettext-map"
def newPhase(_prev: Phase) = new MapPhase(_prev)
class MapPhase(prev: Phase) extends StdPhase(prev) {
override def name = phaseName
def apply(unit: CompilationUnit) {
val shouldExtract = !i18n_class.isEmpty && emptyOutputFileExists
if (shouldExtract) {
// Scala 2.10:
val i18nType = rootMirror.getClassByName(stringToTypeName(i18n_class)).tpe
// Scala 2.11:
//val i18nType = rootMirror.getClassByName(TypeName(i18n_class)).tpe
for (tree @ Apply(Select(x1, x2), list) <- unit.body) {
if (x1.tpe <:< i18nType) {
val methodName = x2.toString
val pos = tree.pos // scala.tools.nsc.util.OffsetPosition
val line = (relPath(pos.source.path), pos.line)
if (i18n_t.contains(methodName)) {
val msgid = fixBackslashSingleQuote(list(0).toString)
msgToLines.addBinding((None, msgid, None), line)
} else if (i18n_tn.contains(methodName)) {
val msgid = fixBackslashSingleQuote(list(0).toString)
val msgidPlural = fixBackslashSingleQuote(list(1).toString)
msgToLines.addBinding((None, msgid, Some(msgidPlural)), line)
} else if (i18n_tc.contains(methodName)) {
val msgctxt = fixBackslashSingleQuote(list(0).toString)
val msgid = fixBackslashSingleQuote(list(1).toString)
msgToLines.addBinding((Some(msgctxt), msgid, None), line)
} else if (i18n_tcn.contains(methodName)) {
val msgctxt = fixBackslashSingleQuote(list(0).toString)
val msgid = fixBackslashSingleQuote(list(1).toString)
val msgidPlural = fixBackslashSingleQuote(list(2).toString)
msgToLines.addBinding((Some(msgctxt), msgid, Some(msgidPlural)), line)
}
}
}
}
}
private def relPath(absPath: String) = {
val curDir = System.getProperty("user.dir")
val relPath = absPath.substring(curDir.length)
val unixPath = relPath.replace("\\", "/") // Windows uses '\' to separate
"../../../.." + unixPath // po files should be put in src/main/resources/i18n directory
}
/**
* t("Don't go") will be extracted as "Don\'t go"
* (including the surrounding double quotes).
*
* Poedit will report "invalid control sequence" for key "Don\'t go", so
* we should change it to just "Don't go".
*/
private def fixBackslashSingleQuote(s: String): String = {
s.replaceAllLiterally("""\'""", "'")
}
}
}
private object ReduceComponent extends PluginComponent {
val global: Xgettext.this.global.type = Xgettext.this.global
val runsAfter = List("jvm")
val phaseName = "xgettext-reduce"
def newPhase(_prev: Phase) = new ReducePhase(_prev)
class ReducePhase(prev: Phase) extends StdPhase(prev) {
override def name = phaseName
def apply(unit: CompilationUnit) {
val shouldExtract = !i18n_class.isEmpty && emptyOutputFileExists
if (shouldExtract && !reduced) {
val builder = new StringBuilder(HEADER)
// Sort by key (msgctxto, msgid, msgidPluralo)
// so that it's easier too see diffs between versions of the .pot/.po file
val sortedMsgToLines = msgToLines.toSeq.sortBy { case (k, v) => k }
for (((msgctxto, msgid, msgidPluralo), lines) <- sortedMsgToLines) {
val sortedLines = lines.toSeq.sorted
for ((srcPath, lineNo) <- sortedLines) {
builder.append("#: " + srcPath + ":" + lineNo + "\n")
}
if (msgctxto.isDefined) builder.append("msgctxt " + msgctxto.get + "\n")
builder.append("msgid " + msgid + "\n")
if (msgidPluralo.isDefined) builder.append("msgid_plural " + msgidPluralo.get + "\n")
builder.append("msgstr \"\"" + "\n\n")
}
val out = new BufferedWriter(new FileWriter(outputFile))
out.write(builder.toString)
out.close()
println(OUTPUT_FILE + " created")
reduced = true
}
}
}
}
}