All Downloads are FREE. Search and download functionalities are using the official Maven repository.

skinny.micro.contrib.I18nSupport.scala Maven / Gradle / Ivy

package skinny.micro.contrib

import java.util.Locale

import skinny.micro.base.BeforeAfterDsl
import skinny.micro.context.SkinnyContext
import skinny.micro.contrib.i18n.Messages
import skinny.micro.{ SkinnyMicroBase, SkinnyMicroException }

/**
 * i18n support.
 */
trait I18nSupport { this: SkinnyMicroBase with BeforeAfterDsl =>

  import I18nSupport._

  before() {
    request(context)(LocaleKey) = resolveLocale(context)
    request(context)(MessagesKey) = provideMessages(locale(context))(context)
  }

  def locale(implicit ctx: SkinnyContext): Locale = {
    if (request(ctx) == null) {
      throw new SkinnyMicroException("There needs to be a request in scope to call locale")
    } else {
      request(ctx).get(LocaleKey).map(_.asInstanceOf[Locale]).orNull
    }
  }

  def userLocales(implicit ctx: SkinnyContext): Array[Locale] = {
    if (request(ctx) == null) {
      throw new SkinnyMicroException("There needs to be a request in scope to call userLocales")
    } else {
      request(ctx).get(UserLocalesKey).map(_.asInstanceOf[Array[Locale]]).orNull
    }
  }

  def messages(key: String)(implicit ctx: SkinnyContext): String = messages(ctx)(key)

  def messages(implicit ctx: SkinnyContext): Messages = {
    if (request(ctx) == null) {
      throw new SkinnyMicroException("There needs to be a request in scope to call messages")
    } else {
      request(ctx).get(MessagesKey).map(_.asInstanceOf[Messages]).orNull
    }
  }

  /**
   * Provides a default Message resolver
   *
   * @param locale Locale used to create instance
   * @return a new instance of Messages, override to provide own implementation
   */
  def provideMessages(locale: Locale)(implicit ctx: SkinnyContext): Messages = Messages(locale)

  /*
  * Resolve Locale based on HTTP request parameter or Cookie
  */
  private def resolveLocale(implicit ctx: SkinnyContext): Locale = {
    resolveHttpLocale(ctx).getOrElse(defaultLocale)
  }

  /*
   * Get locale either from HTTP param, Cookie or Accept-Language header.
   *
   * If locale string is found in HTTP param, it will be set
   * in cookie. Later requests will read locale string directly from this
   *
   * If it's not found, then look at Accept-Language header.
   * Locale strings are transformed to [[java.util.Locale]]
   */
  private def resolveHttpLocale(implicit ctx: SkinnyContext): Option[Locale] = {
    val maybeLocaleValue: Option[String] = {
      (params(ctx).get(LocaleKey) match {
        case Some(localeValue) =>
          cookies(ctx).set(LocaleKey, localeValue)
          Some(localeValue)
        case _ => cookies(ctx).get(LocaleKey)
      })
    }
    maybeLocaleValue
      .map(localeValue => localeFromString(localeValue)(ctx))
      .orElse(resolveHttpLocaleFromUserAgent(ctx))
  }

  /**
   * Accept-Language header looks like "de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4"
   * Specification see [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html]]
   *
   * @return first preferred found locale or None
   */
  private def resolveHttpLocaleFromUserAgent(implicit ctx: SkinnyContext): Option[Locale] = {
    request(ctx).headers.get("Accept-Language") map { s =>
      val locales = s.split(",").map(s => {
        def splitLanguageCountry(s: String): Locale = {
          val langCountry = s.split("-")
          if (langCountry.length > 1) {
            new Locale(langCountry.head, langCountry.last)
          } else {
            new Locale(langCountry.head)
          }
        }
        // If this language has a quality index:
        if (s.indexOf(";") > 0) {
          val qualityLocale = s.split(";")
          splitLanguageCountry(qualityLocale.head)
        } else {
          splitLanguageCountry(s)
        }
      })
      // save all found locales for later user
      request(ctx).setAttribute(UserLocalesKey, locales)
      // We assume that all accept-languages are stored in order of quality
      // (so first language is preferred)
      locales.head
    }
  }

  /**
   * Reads a locale from a String
   * @param in a string like en_GB or de_DE
   */
  private def localeFromString(in: String)(implicit ctx: SkinnyContext): Locale = {
    val token = in.split("_")
    new Locale(token.head, token.last)
  }

  private def defaultLocale: Locale = Locale.getDefault

}

object I18nSupport {

  val LocaleKey: String = "skinny.micro.i18n.locale"

  val UserLocalesKey: String = "skinny.micro.i18n.userLocales"

  val MessagesKey: String = "messages"

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy