-
-
Save tg44/272c042b7af1f0ff12a3a0586ee64c07 to your computer and use it in GitHub Desktop.
i18n scala custom impl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//please if you delete these refactor the corresponding test too | |
test = 5 | |
ok = "ok" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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