3

In my Scala project I use Twirl template engine. Template files structure is duplicated for russian and english languages so for example I have the following two paths: en.Send.txt.MonoEnsure and ru.Send.txt.MonoEnsure

In my code I want to be able to dynamically load en or ru template, something like this:

def render(lang: String) = lang.Send.txt.MonoEnsure("hi")
render("en") // does not work, just to illustrate my point

How can I achieve this?

src091
  • 2,479
  • 5
  • 35
  • 68
  • Why wouldn't you use i18n instead? One template, multiple message files: http://www.playframework.com/documentation/2.3.x/ScalaI18N – Michael Zajac Jul 04 '14 at 16:19
  • @LimbSoup I use Twirl separately from Play framework and my project is not a web application. I'm not sure I can use application.conf in this setting. – src091 Jul 04 '14 at 16:22

2 Answers2

3

This is not a direct answer to your question, but a different solution to solve your problem. The other answer addresses how to technically achieve what you want, but using reflection to do some internationalization is unnecessarily fragile and definitely not recommended.

As is mentioned in the comments, when you use Twirl in the context of a Play app, Play provides you with its own way to do internationalization. Since you don't use Play in your application, you can't use that. But you don't need to use Play's specific way to do internationalization. You can easily build your own rudimentary internationalization construct without pulling in extra dependencies, which I will show in this answer.

First and foremost, this approach should be much more DRY. Secondly, it decouples your layout from the language you happen to be using. Lastly, it's completely typesafe and does not use reflection.

First, make a class representing a language, wrapping a Properties object.

// Language.scala
class Language(filename: String) {
   val properties = new java.util.Properties()
   properties.loadFromXML(new FileInputStream(filename))

   def apply(key: String) = properties.getProperty(key, s"Key $key not found.")
}

object Language {
   val English = new Language("path/to/english.xml")
   val Russian = new Language("path/to/russian.xml")
}

Also define some translations using the Properties XML format:

<!-- path/to/english.xml -->
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
   <entry key="hello.world">Hello World</entry>
</properties>

<!-- path/to/russian.xml -->
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
   <entry key="hello.world">привет мир</entry>
</properties>

Then, when rendering, you can put the language you are using implicitly on the scope:

implicit val language: Language = determineLanguage() // Your logic for determining the language you want to use

// Do other things here..

// Render template
Send.txt.MonoEnsure("hi")

Then, in your template, use the language like this:

@(arg1: Any, arg2: Any)(implicit lang: Language)

<html>
   <body>
      <p>@lang("hello.world")</p>
   </body
</html>

Which, will output a page displaying "Hello World" or "привет мир", depending on which language was chosen.

Update: My first suggestion was to use Java's default properties file format, but it turns out it uses Latin-1 encoding which does not support Cyrillic characters, making it difficult to use with Russian. Therefore, I've updated my answer to instead use the (unfortunately more verbose) Properties XML format, which uses UTF-8 encoding and therefore supports Cyrillic characters.

DCKing
  • 4,053
  • 2
  • 23
  • 41
3

I think this is the code that should achieve that:

import play.twirl.api.Template1

def getTemplate[T](name : String)(implicit man: Manifest[T]) : T =
  Class.forName(name + "$").getField("MODULE$").get(man.erasure).asInstanceOf[T]

def render(lang: String) = 
  getTemplate[Template1[String,String]](s"$lang.Send.txt.MonoEnsure").render("hi")

render("en")

Templates are compiled to BaseScalaTemplate so you can call it using reflection. You only need to know the number of params of your template so that you can load it as instance of trait play.api.twirl.TemplateX. In this case Template1[String, String] (first String for the parameter and second one for the type of the render response).

Check this thread for more info about reflection in scala How do I call a Scala Object method using reflection?

Community
  • 1
  • 1
moliware
  • 9,478
  • 3
  • 32
  • 46