-
Notifications
You must be signed in to change notification settings - Fork 9
Using with Scala
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" % VERSIONYou 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_USforen_US; - Language match, e.g.
en_USforen_GBifen_GBitself 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.
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 adjustMessages.compiled("...")appropriately. -
templateTarget in Compile— specifies a location where*.potfile will be placed. Defaults to:-
target/scala-.../messages/main.potifcrossPaths == true, wheremainis configuration name -
target/messages/main.pototherwise.
-
-
sourceDirectories in (Compile, compileLocales)— specifies directories to search for*.pofiles. Defaults tosrc/main/locales, wheremainis configuration name.