-
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" % 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
foren_US
; - Language match, e.g.
en_US
foren_GB
ifen_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.
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*.pot
file will be placed. Defaults to:-
target/scala-.../messages/main.pot
ifcrossPaths == true
, wheremain
is configuration name -
target/messages/main.pot
otherwise.
-
-
sourceDirectories in (Compile, compileLocales)
— specifies directories to search for*.po
files. Defaults tosrc/main/locales
, wheremain
is configuration name.