Localized Error Messages
Eventually you have to turn Error1 | Error2 | Error3
into something that is
readable by the user.
The best way I have found so far is to use union-derivation.
Example
Defining the localization trait
import yantl.*
import io.github.irevive.union.derivation.*
enum LocaleEnum { case En }
trait LocalizedTextOfValue[A] {
def text(a: A): LocaleEnum ?=> String
}
object LocalizedTextOfValue {
/** Creates a new instance. */
def of[A](localize: (A, LocaleEnum) => String): LocalizedTextOfValue[A] = new {
override def text(value: A): LocaleEnum ?=> String =
(locale: LocaleEnum) ?=> localize(value, locale)
}
inline given derivedUnion[A](using IsUnion[A]): LocalizedTextOfValue[A] =
UnionDerivation.derive[LocalizedTextOfValue, A]
}
extension [A](a: A) {
def localized(using loc: LocalizedTextOfValue[A], locale: LocaleEnum): String = loc.text(a)
}
Defining the newtype
object Age extends Newtype.ValidatedOf(Validator.of(ValidatorRule.between(0L, 100L)))
type Age = Age.Type
Defining the error messages
given LocalizedTextOfValue[ValidatorRule.SmallerThan[Long]] = LocalizedTextOfValue.of {
case (err, LocaleEnum.En) => s"Must be actually born."
}
given LocalizedTextOfValue[ValidatorRule.LargerThan[Long]] = LocalizedTextOfValue.of {
case (err, LocaleEnum.En) => s"Sorry, too old. Must be under ${err.maximum}, was ${err.actual}."
}
Localizing the error message
given LocaleEnum = LocaleEnum.En
Age.make(-1).left.map(errors => errors.map(_.localized))
// res0: Either[Vector[String], Type] = Left(
// value = Vector("Must be actually born.")
// )
Age.make(105).left.map(errors => errors.map(_.localized))
// res1: Either[Vector[String], Type] = Left(
// value = Vector("Sorry, too old. Must be under 100, was 105.")
// )