[scala]spring messagesource的功能模仿实现


原理

  • 遍历目录下messages*.properties文件
  • 从文件名中取出locale,比如messages_en_US.properties中en和US分别为语言名和国家名
  • 解析properties文件的k=v键值对,有等号,k和v不为空的情况下构造一个Map,k其实为messageCode, v为messageFormat
  • 最后是根据locale和messageCode查询messageFormat

由于scala有强大的option,flatMap等方法,spring的messageSource的defaultMessage作为调用者的可选项,即Option.getOrElse,参照代码最下方的示例

测试结果

Some(Hello, XnnYygn!)
Some(Hello, XnnYygn!!!)
Some(你好,XnnYygn!)
default message

源代码

import java.util.Locale
import java.text.MessageFormat
import java.io.{File => JFile, FilenameFilter}
import scala.io.Source

trait MessageSource {
  def get(messageCode: String, locale: Locale, 
    arguments: Array[Any]): Option[String]
}

abstract class AbstractMessageSource extends MessageSource{
  protected def getMessageFormat(
    messageCode: String, locale: Locale): Option[MessageFormat]

  def get(messageCode: String, locale: Locale, 
      arguments: Array[Any] = Array[Any]()) = {
    getMessageFormat(messageCode, locale).map(_.format(arguments))
  }
}

class PropertyFilesMessageSource(
    formats: Map[Locale, Map[String, MessageFormat]]) 
    extends AbstractMessageSource {
  protected def getMessageFormat(messageCode: String, locale: Locale) = 
    formats.get(locale).flatMap(_.get(messageCode))
}

object PropertyFilesMessageSource {
  private final val PROPERTIES_NAME_REGEX = 
    "^messages(_([a-z]+))?(_([A-Z]+))?\\.properties$".r

  def apply(dir: JFile, encoding: String): PropertyFilesMessageSource = {
    new PropertyFilesMessageSource(dir.listFiles.flatMap {f =>
      PROPERTIES_NAME_REGEX.findFirstMatchIn(f.getName).map {m =>
        val language = nullToEmpty(m.group(2))
        val country = nullToEmpty(m.group(4))
        (new Locale(language, country), parseMessages(f, encoding))
      }
    }.toMap)
  }

  private def nullToEmpty(str: String): String = Option(str).getOrElse("")

  private def parseMessages(
      f: JFile, encoding: String): Map[String, MessageFormat] = {
    Source.fromFile(f, encoding).getLines.flatMap { line =>
      val index = line.indexOf('=')
      if(index <= 0) None
      else {
        val key = line.substring(0, index).trim
        val value = line.substring(index + 1).trim
        if(key.length == 0 || value.length == 0) None
        else Some((key, new MessageFormat(value)))
      }
    }.toMap
  }
}

object MessageSourceRunner {
  def main(args: Array[String]): Unit = {
    val ms = PropertyFilesMessageSource(new JFile("messages"), "UTF-8")
    println(ms.get("greeting", new Locale("", ""), Array("XnnYygn")))
    println(ms.get("greeting", Locale.US, Array("XnnYygn")))
    println(ms.get("greeting", Locale.SIMPLIFIED_CHINESE, Array("XnnYygn")))
    println(ms.get(
      "no_such_message", Locale.getDefault).getOrElse("default message"))
  }
}
,