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

scaposer.I18n.scala Maven / Gradle / Ivy

The newest version!
package scaposer

object I18n {
  /**
   * Parsed [[Translation]]s contains comments, we strip them to reduce memory usage.
   * We don't need plural ID either as we only use (context, singular ID) as key
   * to lookup translated strings.
   */
  def apply(translations: Seq[Translation]): I18n = {
    // Strip unneccessary info and convert translations
    // to map of (ctx, singular) -> strs
    val ctxSingularToStrs = translations.foldLeft(
      Map.empty[(String, String), Seq[String]]
    ) { (acc, t) =>
      val key_value = t match {
        case SingularTranslation(_, ctx, _, singular, _, str, _) =>
          (ctx, singular) -> Seq(str)

        case PluralTranslation(_, ctx, _, singular, _, _, _, strs, _) =>
          (ctx, singular) -> strs
      }
      acc + key_value
    }

    I18n(ctxSingularToStrs)
  }
}

case class I18n(ctxSingularToStrs: Map[(String, String), Seq[String]]) {
  def ++(other: I18n): I18n = {
    // Note the order of "++", "other" will overwrite "this"
    val newMap = ctxSingularToStrs ++ other.ctxSingularToStrs

    // Ensure that pluralForms is not lost because "other" does not have it
    if (other.pluralFormso.isDefined) {
      I18n(newMap)
    } else {
      if (pluralFormso.isDefined) {
        val key   = ("", "")
        val value = ctxSingularToStrs(key)
        I18n(ctxSingularToStrs + (key -> value))
      } else {
        I18n(newMap)
      }
    }
  }

  def t(singular: String): String = lookupSingular("", singular)

  def tc(ctx: String, singular: String): String = lookupSingular(ctx, singular)

  def tn(singular: String, plural: String, n: Long): String = lookupPlural("", singular, plural, n)

  def tcn(ctx: String, singular: String, plural: String, n: Long): String = lookupPlural(ctx, singular, plural, n)

  override def toString = {
    val buffer = new StringBuilder
    buffer.append("\n")
    ctxSingularToStrs.foreach { case ((ctx, singular), strs) =>
      if (!ctx.isEmpty) {
        buffer.append(ctx)
        buffer.append("\n")
      }

      buffer.append(singular)
      buffer.append("\n")

      if (strs.size == 1) {
        buffer.append(strs.head)
        buffer.append("\n")
      } else {
        strs.zipWithIndex.foreach { case (str, index) =>
          buffer.append(index)
          buffer.append(" ")
          buffer.append(str)
          buffer.append("\n")
        }
      }
      buffer.append("\n")
    }
    buffer.toString
  }

  //----------------------------------------------------------------------------

  private def lookupSingular(ctx: String, singular: String): String = {
    ctxSingularToStrs.get((ctx, singular)) match {
      case Some(strs) =>
        // Newly created .pot/.po files have keys but the values are all empty
        val str = strs.head
        if (str.isEmpty) singular else str

      case None =>
        // Fallback
        if (ctx.isEmpty)
          singular
        else
          lookupSingular("", singular)  // Try translation without context
    }
  }

  private def lookupPlural(ctx: String, singular: String, plural: String, n: Long): String = {
    ctxSingularToStrs.get((ctx, singular)) match {
      case Some(strs) =>
        val index = evaluatePluralForms(n)
        if (index >= strs.size) {
          plural
        } else {
          // Newly created .pot/.po files have keys but the values are all empty
          val str = strs(index)
          if (str.isEmpty) singular else str
        }

      case None =>
        // Fallback
        if (ctx.isEmpty)
          if (n != 1) plural else singular       // English rule (not "n > 1"!)
        else
          lookupPlural("", singular, plural, n)  // Try translation without context
    }
  }

  //----------------------------------------------------------------------------

  // See evaluatePluralForms below
  private val pluralFormso: Option[String] = {
    ctxSingularToStrs.get(("", "")) match {
      case None => None

      case Some(strs) =>
        val header = strs.head
        //linesIterator is undeprecated in 2.13 to avoid ambiguity with lines method in JDK11
        header.linesIterator.find(_.startsWith("Plural-Forms")) match {
          case None => None

          case Some(line) =>
            Some(line.replaceFirst("^Plural-Forms:", "").replace(" ", ""))
        }
    }
  }

  // This evaluate method can only work correctly with Plural-Forms exactly listed at:
  // http://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html#Plural-forms
  // http://www.gnu.org/software/gettext/manual/html_node/Translating-plural-forms.html#Translating-plural-forms
  private val evaluatePluralForms: (Long => Int) = {
    if (pluralFormsMatched("nplurals=1; plural=0")) {
      n => 0
    } else if (pluralFormsMatched("nplurals=2; plural=n != 1")) {
      n => if (n != 1) 1 else 0
    } else if (pluralFormsMatched("nplurals=2; plural=n>1")) {
      n => if (n > 1) 1 else 0
    } else if (pluralFormsMatched("nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2")) {
      n => if (n % 10 == 1 && n % 100 != 11) 0 else if (n != 0) 1 else 2
    } else if (pluralFormsMatched("nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2")) {
      n => if (n == 1) 0 else if (n == 2) 1 else 2
    } else if (pluralFormsMatched("nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2")) {
      n => if (n == 1) 0 else if (n == 0 || (n % 100 > 0 && n % 100 < 20)) 1 else 2
    } else if (pluralFormsMatched("nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2")) {
      n => if (n % 10 == 1 && n % 100 != 11) 0 else if (n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20)) 1 else 2
    } else if (pluralFormsMatched("nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2")) {
      n => if (n % 10 == 1 && n % 100 != 11) 0 else if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) 1 else 2
    } else if (pluralFormsMatched("nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2")) {
      n => if (n == 1) 0 else if (n >= 2 && n <= 4) 1 else 2
    } else if (pluralFormsMatched("nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2")) {
      n => if (n == 1) 0 else if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) 1 else 2
    } else if (pluralFormsMatched("nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3")) {
      n => if (n % 100 == 1) 0 else if (n % 100 == 2) 1 else if (n % 100 == 3 || n % 100 == 4) 2 else 3
    } else {
      pluralFormso.map { expr =>
        PluralIndexExpressionParser(expr) match {
          case PluralIndexExpressionParser.Success(result, _) => x: Long => result(x).toInt
          case failure: PluralIndexExpressionParser.NoSuccess => fallbackPluralFormEvaluator _
        }
      }.getOrElse(fallbackPluralFormEvaluator _)
    }
  }
  
  private def fallbackPluralFormEvaluator(n: Long) = 0

  private def pluralFormsMatched(pattern: String) = {
    pluralFormso match {
      case None              => false
      case Some(noSpaceLine) => noSpaceLine.indexOf(pattern.replace(" ", "")) >= 0
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy