I18n

GNU gettext is used. Unlike many other i18n methods, gettext supports plural forms.

_images/poedit.png

Write internationalized messages in source code

xitrum.Action extends xitrum.I18n, which has these methods:

t("Message")
tc("Context", "Message")

t("Hello %s").format("World")

// 1$ and 2$ are placeholders
t("%1$s says hello to %2$s, then %2$s says hello back to %1$s").format("Bill", "Hillary")

// {0} and {1} are placeholders
java.text.MessageFormat.format(t("{0} says hello to {1}, then {1} says hello back to {0}"), "Bill", "Hillary")

t("%,.3f").format(1234.5678)                                // => 1,234.568
t("%,.3f").formatLocal(java.util.Locale.FRENCH, 1234.5678)  // => 1 234,568
// Above, you explicitly specify locale.
// If you want to implicitly use locale of the current action:
// when English => 1,234.568, when French => 1 234,568
t("%,.3f", 1234.5678)

In a action or action, just call them. In other places like models, you need to pass the current action to them and call t and tc on it:

// In an action
respondText(MyModel.hello(this))

// In the model
import xitrum.I18n
object MyModel {
  def hello(i18n: I18n) = i18n.t("Hello World")
}

Extract messages to pot files

Create an empty i18n.pot file in your project’s root directory, then recompile the whole project.

sbt/sbt clean
rm i18n.pot
touch i18n.pot
sbt/sbt compile

sbt/sbt clean is to delete all .class files, forcing SBT to recompile the whole project. Because after sbt/sbt clean, SBT will try to redownload all dependencies, you can do a little faster with the command find target -name *.class -delete, which deletes all .class files in the target directory.

After the recompilation, i18n.pot will be filled with gettext messages extracted from the source code. To do this magic, Scala compiler plugin technique is used.

One caveat of this method is that only gettext messages in Scala source code files are extracted. If you have Java files, you may want to extract manually using xgettext command line:

xgettext -kt -ktc:1c,2 -ktn:1,2 -ktcn:1c,2,3 -o i18n_java.pot --from-code=UTF-8 $(find src/main/java -name "*.java")

Then you manually merge i18n_java.pot to i18n.pot.

Where to save po files

i18n.pot is the template file. You need to copy it to <language>.po files and translate.

Xitrum monitors directories named i18n in classpath. If a <language>.po file in that directory is updated or added at runtime, Xitrum will automatically reload that <language>.po file.

src
  main
    scala
    view
    resources
      i18n
        ja.po
        vi.po
        ...

Use a tool like Poedit to edit po files. You can use it to merge newly created pot file to existing po files.

_images/update_from_pot.png

You can package po files in multiple JAR files. Xitrum will automatically merge them when running.

mylib.jar
  i18n
    ja.po
    vi.po
        ...

another.jar
  i18n
    ja.po
    vi.po
        ...

Set language

  • To get languages set in the Accept-Language request header by the browser, call browserLanguages. The result is sorted by priority set by the brower, from high to low.

  • The default language is “en”. To set language for the current action, for example Japanese, call language = "ja".

  • To autoset the most suitable language in resources, call autosetLanguage(availableLanguages), where availableLanguages is a list of languages corresponding to .po files in resources/i18n directory and JAR files. If there’s no suitable language, the language is still the default “en”.

  • To get the current language set above, use language.

In your action, typically in a before filter, to set language:

beforeFilter {
  val lango: Option[String] = yourMethodToGetUserPreferenceLanguageInSession()
  lango match {
    case None       => autosetLanguage(Locale.forLanguageTag("ja"), Locale.forLanguageTag("vi"))
    case Some(lang) => language = lang
  }
}

Validation messages

jQuery Validation plugin provides i18n error messages. Xitrum automatically include the message file corresponding to the current language.

For server side default validators in xitrum.validator package, Xitrum also provide translation for them.

Plural forms

tn("Message", "Plural form", n)
tcn("Context", "Message", "Plural form", n)

Xitrum can only work correctly with Plural-Forms exactly listed at:

Your plural forms must be exactly one of the following:

nplurals=1; plural=0
nplurals=2; plural=n != 1
nplurals=2; plural=n>1
nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2
nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2
nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2
nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2
nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2
nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2
nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2
nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3

Date and number format

If you use Scalate template engine, by default the date and number format will be the format of the language of the current action.

If you want to use other format:

import java.text.{DateFormat, NumberFormat}

val myDateFormat   = ...
val myNumberFormat = ...
val options        = Map("date" -> myDateFormat, "number" -> myNumberFormat)
respondView(options)