Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Dao Ngoc authored and Dao Ngoc committed Jan 4, 2013
0 parents commit 0e88306
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 0 deletions.
20 changes: 20 additions & 0 deletions MIT-LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright (c) 2013 Ngoc Dao

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 changes: 10 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
This is a Scala 2.10 compiler plugin that acts like GNU xgettext command to extract
i18n strings in Scala source code files to Gettext .po file.

See http://www.scala-lang.org/node/140

Usage
-----

This compiler plugin checks if there's an empty i18n.pot file in the current
working directory, then it will fill that file with string resources.
16 changes: 16 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
organization := "tv.cntt"

name := "scala-xgettext"

version := "1.0-SNAPSHOT"

scalacOptions ++= Seq(
"-deprecation",
"-unchecked"
)

// http://www.scala-sbt.org/release/docs/Detailed-Topics/Cross-Build
//crossScalaVersions := Seq("2.10.0")
scalaVersion := "2.10.0"

libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.10.0"
34 changes: 34 additions & 0 deletions dev/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Publish to Sonatype
-------------------

See:
https://github.com/sbt/sbt.github.com/blob/gen-master/src/jekyll/using_sonatype.md

1. Copy content of
dev/build.sbt.end to the end of build.sbt
dev/plugins.sbt.end to the end of project/plugins.sbt
2. Run ``sbt publish``. Alternatively you can run ``sbt`` then from SBT
command prompt run ``+ publish``.
3. Login at https://oss.sonatype.org/ and from "Staging Repositories" select the
newly published item, click "Close" then "Release".

This workflow is for others to easily do ``sbt publish-local`` without PGP key.
Otherwise there will be error:

::

java.io.FileNotFoundException: ~/.sbt/gpg/secring.asc (No such file or directory)

Publish to local
----------------

While developing, you may need do local publish. Run
``sbt publish-local``.
Alternatively you can run ``sbt`` then from SBT command prompt run
``+ publish-local``.

To delete the local publish:

::

$ find ~/.ivy2 -name *xgettext* -delete
36 changes: 36 additions & 0 deletions dev/build.sbt.end
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Publish to Sonatype -------------------------------------------------------
// https://github.com/sbt/sbt.github.com/blob/gen-master/src/jekyll/using_sonatype.md

publishTo <<= (version) { version: String =>
val nexus = "https://oss.sonatype.org/"
if (version.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "content/repositories/snapshots")
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
}

publishMavenStyle := true

publishArtifact in Test := false

pomIncludeRepository := { _ => false }

pomExtra := (
<url>http://ngocdaothanh.github.com/scala-xgettext/</url>
<licenses>
<license>
<name>MIT</name>
<url>https://github.com/ngocdaothanh/scala-xgettext/blob/master/MIT-LICENSE</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<url>git@github.com:ngocdaothanh/scala-xgettext.git</url>
<connection>scm:git:git@github.com:ngocdaothanh/scala-xgettext.git</connection>
</scm>
<developers>
<developer>
<id>ngocdaothanh</id>
<name>Ngoc Dao</name>
<url>http://cntt.tv</url>
</developer>
</developers>
)
3 changes: 3 additions & 0 deletions dev/plugins.sbt.end
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// http://www.cakesolutions.net/teamblogs/2012/01/28/publishing-sbt-projects-to-nexus/
// https://groups.google.com/forum/?fromgroups=#!topic/simple-build-tool/Z54e0wM6SbU
addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.7")
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.1.1")
4 changes: 4 additions & 0 deletions src/main/resources/scalac-plugin.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<plugin>
<name>scala-xgettext</name>
<classname>scala.Xgettext</classname>
</plugin>
130 changes: 130 additions & 0 deletions src/main/scala/scala/Xgettext.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
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._

val name = "scala-xgettext"
val description = "This Scala compiler plugin extracts and creates gettext.pot file"
val components = List[PluginComponent](MapComponent, ReduceComponent)

val I18N_CLASS_NAME = "scala.I18n"
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 <email@example.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"""

val outputFile = new File(OUTPUT_FILE)
val emptyOutputFileExists = outputFile.exists && outputFile.isFile && outputFile.length == 0
// msgctxt msgid msgid_plural source line
val msgToLines = new MHashMap[(Option[String], String, Option[String]), MSet[(String, Int)]] with MultiMap[(Option[String], String, Option[String]), (String, Int)]
var reduced = false

private object MapComponent extends PluginComponent {
val global: Xgettext.this.global.type = Xgettext.this.global

val runsAfter = List("refchecks")

val phaseName = "scala-xgettext-map"

def newPhase(_prev: Phase) = new MapPhase(_prev)

class MapPhase(prev: Phase) extends StdPhase(prev) {
override def name = phaseName

def apply(unit: CompilationUnit) {
if (emptyOutputFileExists) {
val i18nType = rootMirror.getClassByName(stringToTypeName(I18N_CLASS_NAME)).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 (methodName == "t") {
val msgid = list(0).toString
msgToLines.addBinding((None, msgid, None), line)
} else if (methodName == "tc") {
val msgctxt = list(0).toString
val msgid = list(1).toString
msgToLines.addBinding((Some(msgctxt), msgid, None), line)
} else if (methodName == "tn") {
val msgid = list(0).toString
val msgidPlural = list(1).toString
msgToLines.addBinding((None, msgid, Some(msgidPlural)), line)
} else if (methodName == "tcn") {
val msgctxt = list(0).toString
val msgid = list(1).toString
val msgidPlural = 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
}
}
}

private object ReduceComponent extends PluginComponent {
val global: Xgettext.this.global.type = Xgettext.this.global

val runsAfter = List("jvm")

val phaseName = "scala-xgettext-reduce"

def newPhase(_prev: Phase) = new ReducePhase(_prev)

class ReducePhase(prev: Phase) extends StdPhase(prev) {
override def name = phaseName

def apply(unit: CompilationUnit) {
if (emptyOutputFileExists && !reduced) {
val builder = new StringBuilder(HEADER)

for (((msgctxto, msgid, msgidPluralo), lines) <- msgToLines) {
for ((srcPath, lineNo) <- lines) {
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
}
}
}
}
}

0 comments on commit 0e88306

Please sign in to comment.