Skip to content

Instantly share code, notes, and snippets.

@tg44
Created December 15, 2019 12:24
Show Gist options
  • Save tg44/272c042b7af1f0ff12a3a0586ee64c07 to your computer and use it in GitHub Desktop.
Save tg44/272c042b7af1f0ff12a3a0586ee64c07 to your computer and use it in GitHub Desktop.
i18n scala custom impl
class HoconReadSpec extends TestBase {
"MessageCache" should {
"read HOCON files" in {
val messages = MessageCache.readHocons
messages(Lang("en"))("test") shouldBe "5"
messages(Lang("en"))("ok") shouldBe "ok"
}
}
}
import java.util.Locale
case class Lang(locale: Locale) {
def language: String = locale.getLanguage
def country: String = locale.getCountry
}
object Lang {
val Default = Lang(Locale.getDefault)
def apply(language: String): Lang = Lang(new Locale(language))
def apply(maybeLang: Option[String], default: Lang = Default): Lang = maybeLang.map(apply) getOrElse default
}
import java.io.File
import java.text.MessageFormat
import java.util.concurrent.ConcurrentHashMap
import com.typesafe.config.ConfigFactory
class MessageCache(initial: Map[Lang, Map[String, String]], defaultLang: Lang) {
//loads the initial map to an internal immutableMap[Lang, mutableMap[String,String]] data structure
private val innerState: Map[Lang, ConcurrentHashMap[String, String]] = initial.map {
case (k, v) =>
k -> {
val map = new ConcurrentHashMap[String, String]()
v.foreach { case (vk, vv) => map.put(vk, vv) }
map
}
}
def upsertValue(l: Lang, key: String, value: String): Unit = {
innerState.get(l).map(v => v.put(key, value))
}
private def findMessage(l: Lang, key: String): Option[String] = {
innerState.get(l).flatMap(v => Option(v.get(key)))
}
private def getMessage(l: Lang, key: String): String = {
findMessage(l, key).orElse(findMessage(defaultLang, key)).getOrElse(key)
}
def raw(msg: String)(implicit lang: Lang): String = {
getMessage(lang, msg)
}
def apply(msg: String, args: Any*)(implicit lang: Lang): String = {
new MessageFormat(raw(msg), lang.locale).format(args.map(_.asInstanceOf[java.lang.Object]).toArray)
}
override def toString: String = {
s"MessageCache(defaultLang: $defaultLang, innerState: $innerState)"
}
}
object MessageCache {
/**
* If you import com.digitlean.service.i18n.MessageCache._
* You can write t"test.deeper.text" if you have a MessageCache and a Lang in the implicit scope!
*/
implicit class TranslationHelper(val sc: StringContext) extends AnyVal {
def t(args: Any*)(implicit lang: Lang, cache: MessageCache): String = {
val strings = sc.parts.iterator
val expressions = args.iterator
var buf = new StringBuffer(strings.next)
while (strings.hasNext) {
buf append expressions.next
buf append strings.next
}
cache.raw(buf.toString)
}
}
/**
* You don't really want to understand this...
* It basically lists all the files in i18n dir.
* Then it parses back the wellnamed ones as a map.
*/
def readHocons(): Map[Lang, Map[String, String]] = {
val dirName = "i18n"
val filenamePrefix = "messages."
val filenamePostfix = ".conf"
val resourceFiles = listResourceDir(dirName)
val files = if (resourceFiles.nonEmpty) {
resourceFiles
} else {
val localFolder = new File(s"./src/main/resources/$dirName")
if (localFolder.exists && localFolder.isDirectory) {
localFolder.listFiles.toList
} else {
List.empty
}
}
val langsAsString = files
.filter(_.getName.startsWith(filenamePrefix))
.filter(_.getName.endsWith(filenamePostfix))
.map(_.getName.replace(filenamePrefix, "").replace(filenamePostfix, ""))
val langs = langsAsString.map(s => Lang(s))
langs
.map(
lang =>
lang -> {
val config = ConfigFactory
.parseURL(getClass.getClassLoader.getResource(s"$dirName/$filenamePrefix${lang.locale}$filenamePostfix"))
.resolve()
.entrySet()
import scala.collection.JavaConverters._
config.asScala
.map(e => e.getKey -> String.valueOf(e.getValue.unwrapped()))
.toMap
}
)
.toMap
}
// http://www.uofr.net/~greg/java/get-resource-listing.html
private def listResourceDir(dirName: String): List[File] = {
val classloader = getClass.getClassLoader
val dirURL = classloader.getResource(dirName)
if (dirURL != null && dirURL.getProtocol.equals("file")) /* A file path: easy enough */ {
new File(dirURL.toURI).listFiles.toList
} else if (dirURL == null) {
Nil
} else if (dirURL.getProtocol.equals("jar")) {
import collection.JavaConverters._
import java.net.URLDecoder
import java.util.jar.JarFile
val jarPath = dirURL.getPath.substring(5, dirURL.getPath.indexOf("!")) //strip out only the JAR file
val jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"))
val entries = jar.entries //gives ALL entries in jar
entries.asScala.filter(e => e.getName.startsWith(dirName)).filter(e => e.getName != dirName).toList.flatMap { e =>
if (e.getName.endsWith("/")) {
Nil
} else {
new File(classloader.getResource(e.getName).getPath) :: Nil
}
}
} else {
Nil
}
}
}
//please if you delete these refactor the corresponding test too
test = 5
ok = "ok"
class TranslateInterpolationSpec extends TestBase {
"MessageCache" should {
"interpolate strings correctly" in {
import MessageCache._
implicit val messages: MessageCache =
new MessageCache(Map(Lang("en") -> Map("test" -> "ok", "deeper.test.str" -> "still ok")), Lang("en"))
implicit val lang = Lang("en")
t"test" shouldBe "ok"
t"deeper.test.str" shouldBe "still ok"
}
"fallbacks correctly" in {
import MessageCache._
implicit val messages: MessageCache =
new MessageCache(Map(Lang("en") -> Map("test" -> "ok", "deeper.test.str" -> "still ok")), Lang("en"))
val a = {
implicit val lang = Lang("en")
t"testNotHere" shouldBe "testNotHere"
}
val b = {
implicit val lang = Lang("hu")
t"test" shouldBe "ok"
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment