Argument passing

Babel has helpers like StringFormat1, StringFormat2, …, StringFormat22. to help you inject arguments into Strings. In that regard it is similar to java.lang.String.format or java.text.MessageFormat.format but provides better compile time checks.

Babel’s StringFormat does not automatically render arbitrary types (as String.format does for numeric values). It only accepts String values, so the caller is responsible for converting the value to a renderable format beforehand.

  • Parameter placeholders are encoded in the style of java.text.MessageFormat, starting at index 0 (e.g. {0}, {1}, …)
  • Each placeholder index may be used at most once (e.g. "lorem {0} dolar {0}" is not allowed)
  • The order of the placeholders may vary across translations (e.g. "The {0} {1} is fast" and "El {1} {0} es rapido")
  • There can not be more placeholders than arguments (e.g. StringFormat1 can not be decoded when there is {0} and {1} as there is only 1 parameter)
  • It is a decoding error to define placeholder indices that exceed the StringFormatN arity (e.g. StringFormat2 may only use {0} and {1}, but not {2})
  • Not all placeholders must be used (e.g. "Good afternoon, {0}. Today it's {1} degrees." and "Guten Tag, sehr geehrte Damen und Herren. Heute sind es {1} Grad.")
en.conf
sourceweather = "The temperature in {0} is {1} degrees Celsius."
de.conf
sourceweather = "Es sind {1} Grad Celsius in {0}."
Note

In the German version, the parameters are called in swapped order.

import cats.effect._
import cats.effect.unsafe.implicits.global
import cats.syntax.all._
import io.taig.babel._
import io.taig.babel.generic.auto._

final case class I18n(weather: StringFormat2)

val i18ns = Loader
  .default[IO]
  .load("arguments", Set(Locales.en, Locales.de))
  .map(Decoder[I18n].decodeAll)
  .rethrow
  .map(_.withFallback(Locales.en))
  .flatMap(_.liftTo[IO](new IllegalStateException("Translations for en missing")))
  .unsafeRunSync()
// i18ns: NonEmptyTranslations[I18n] = NonEmptyTranslations(en -> I18n(The temperature in {0} is {1} degrees Celsius.),Translations(Map(de -> I18n(Es sind {1} Grad Celsius in {0}.))))
i18ns(Locales.en).weather("Cape Town", "26")
// res0: String = "The temperature in Cape Town is 26 degrees Celsius."
i18ns(Locales.de).weather("Frankfurt am Main", "18")
// res1: String = "Es sind 18 Grad Celsius in Frankfurt am Main."

Compile-time guarantees

In the above example we have used StringFormat2, which means that exactly 2 arguments must be passed. Otherwise a compile error will occur.

i18ns(Locales.en).weather("Stockholm")
// error: 
// missing argument for parameter v1 of method apply in class StringFormat2: (v0: String, v1: String): String

However, it is allowed to omit parameters.

Decoder[StringFormat2]
  .decode(Babel.text("It's always rainy in {0}"))
  .map(_.apply("London", "3"))
// res3: Either[Error, String] = Right(value = "It's always rainy in London")