Skip to content

Using with Scala

Maxim Karpov edited this page Feb 9, 2017 · 4 revisions

To use Scalingua with plain Scala project (e.g. console or GUI application, for Play web applications use this guide), you need to include Scalingua both as library dependency and SBT plugin. To do this, you should add following lines to build configuration:


project/plugins.sbt:

addSbtPlugin("ru.makkarpov" % "scalingua-sbt" % VERSION)

build.sbt:

enablePlugins(Scalingua)

libraryDependencies += "ru.makkarpov" % "scalingua" % VERSION

You can find the most recent version in the readme.md. To be able to use translations, you should define implicit Messages somewhere:


SomeGlobal.scala:

object SomeGlobal {
  val messages = Messages.compiled()
  // This assumes that you did not change the default settings of SBT key `localePackage`
  // Otherwise you should pass an actual value of it as argument of `compiled`
}

You should also define implicit LanguageId , since Messages hold translations of all languages, and LanguageId will select which one to use. You can define it globally (which makes sense if language is chosen for entire application) or pass it as implicit argument. A language ID is just a pair of two strings — the first one is a language code, the second one is a country code. Messages will match supplied language ID with available ones in the following precedence:

  • Exact match, e.g. en_US for en_US;
  • Language match, e.g. en_US for en_GB if en_GB itself is not available;
  • English fallback.

You can construct language ID either using form LanguageId("ru", "RU") or using LanguageId("ru-RU"). The latter is convenient for settings storage, since langId.toString return string that always could be passed as argument to constructor. After you obtained LanguageId instance, you should import ru.makkarpov.scalingua.I18n._ and enjoy!

The singular forms could be translated either by using string interpolator (most convenient way, just one letter to get your string translated and automatic placeholders for variables) or manually using t and tc functions (latter takes a message context as it first argument, which would be copied into msgctxt declaration of *.pot file) as follows:


Example.scala:

import SomeGlobal._ // For Messages
import ru.makkarpov.scalingua.I18n._

object Example extends App {
  implicit val lang = LanguageId("..-..")

  println(t"Hello, world!")
  // Will create entry "Hello, world!" in *.pot file

  print(t"Enter your name: ")
  // Will create entry "Enter your name: " in *.pot file

  val name = Console.in.readLine()
  println(t"Hello, $name!")
  // Will create entry "Hello, %(name)!" in *.pot file

  println(t"2 + 2 is ${2 + 2}")
  // Will not compile since no name is given for `2 + 2` expression

  println(t"2 + 2 is ${2 + 2}%(value)")
  // Will create entry "2 + 2 is %(value)" in *.pot file
}

t and tc functions can be convenient in some situations, but since they are implemented as macros to support extraction of messages, they have restrictions on parameters that could be passed to it:


Example.scala:

import SomeGlobal._
import ru.makkarpov.scalingua.I18n._

object Example extends App {
  println(t("Hello, world!"))
  // Works well

  val str = "A message!"
  println(t(str))
  // Will not compile since `str` is not a string literal

  println(t(
    """Some
      |multiline
      |string""".stripMargin))
  // The only exception to this rule is a strings in a `"...".stripMargin` or
  // `"...".stripMargin('...')` form.

  println(t("Hello, %(name)!"))
  // Will not compile since variable `name` is not defined

  println(t("Hello, %(name)!", "name" -> /* ... */))
  // Works well

  println(t("Hello, world!", "name" -> /* ... */))
  // Will not compile since variable `name` do not appears at
  // interpolation string.

  val key = "name"
  println(t("Hello, %(name)", key -> /* ... */))
  // The same rule for string literals -- it will not compile.

  println(tc("some", "Hello, world!"))
  // The same rules applies for first argument -- only string literals.
  // This will produce following entry in `*.pot` file:
  //   #: Example.scala:39
  //   msgctxt "some"
  //   msgid "Hello, world!"
  //   msgstr ""
}

The translation of plural strings looks can be done either by using string interpolator or, similarly to singular forms, functions p and pc:


Example.scala:

import SomeGlobal._
import ru.makkarpov.scalingua.I18n._

object Example extends App {
  val n = Console.in.readLine().toInt

  println(p"There is $n dog${S.s}")
  // Works well and produces following entry in `*.pot` file:
  //   #: Example.scala:100
  //   msgid "There is %(n) dog"
  //   msgid_plural "There is %(n) dogs"
  //   msgstr[0] ""
  //   msgstr[1] ""
  
  println(p("I have %(n) cat", "I have %(n) cats", n))
  // Similar to above, but specifies plural form explictly

  val k = 10
  println(p"${n.nVar} $k")
  // When multiple integer variables are present, one that
  // represents plural number must be selected using `.nVar`
}

Here the S.s (and also S.es) is a pural suffix — an expression of special type (either Suffix.S or Suffix.ES). When macro will see this expression in interpolation string, it will insert the specified suffix in plural string and remove this variable from interpolation. So p"I have $n dog${S.s}" becomes two strings — "I have %(n) dog" and "I have %(n) dogs". Macros will understand that integer variable n means the plural number itself. But if you have multiple integers or longs as interpolation variables, macros will throw error because it's ambiguous. In this case you will need to annotate one of your variables as plural number by specifying .nVar after it.

The same restrictions for literals applies to p and pc functions. When SBT plugin will compile your translated *.po file, it will take header field Plural-Forms into account and generate Scala function based on it. This function will select actual plural form.

To translate your application, you should put *.po files under src/main/locales directory, and name them with code of language that they contains, e.g. src/main/locales/ru_RU.po. SBT plugin will pick these *.po files and compile them into efficient Scala classes and binary data.

SBT plugin settings

Currently SBT plugin has a number of properties to configure:

  • localePackage in Compile — specifies a package for all locale classes and resources, defaults to "locales". You should change this to avoid namespace conflicts, but adjust Messages.compiled("...") appropriately.
  • templateTarget in Compile — specifies a location where *.pot file will be placed. Defaults to:
    • target/scala-.../messages/main.pot if crossPaths == true, where main is configuration name
    • target/messages/main.pot otherwise.
  • sourceDirectories in (Compile, compileLocales) — specifies directories to search for *.po files. Defaults to src/main/locales, where main is configuration name.
Clone this wiki locally