Scala 播放2.4+;i18n:使用数据库而不是属性文件进行国际化

Scala 播放2.4+;i18n:使用数据库而不是属性文件进行国际化,scala,internationalization,playframework-2.4,playframework-2.5,Scala,Internationalization,Playframework 2.4,Playframework 2.5,我所做的很好(至少看起来是这样),但我不相信这是最好的方法。。。 基本上,我希望将我的i18n翻译放在数据库中,而不是属性文件中,这样用户可以轻松编辑这些翻译,缓存可以在短时间内将它们提供给其他用户-我使用Akka Actor从数据库中读取,并创建messageApi使用的缓存(之前,我总是需要根据属性文件中的更改重新部署) 基本上,我所做的完全是错误的吗? TranslationActor.scala: TranslationGuiceConfiguration.scala:(来自com.go

我所做的很好(至少看起来是这样),但我不相信这是最好的方法。。。 基本上,我希望将我的i18n翻译放在数据库中,而不是属性文件中,这样用户可以轻松编辑这些翻译,缓存可以在短时间内将它们提供给其他用户-我使用Akka Actor从数据库中读取,并创建messageApi使用的缓存(之前,我总是需要根据属性文件中的更改重新部署)

基本上,我所做的完全是错误的吗?

TranslationActor.scala:

TranslationGuiceConfiguration.scala:(来自com.google.inject.AbstractModule)

然后我在中扩展了DefaultMessagesApi的一部分(通过查看MessagesApi的代码) MessagesPersoApi.scala:

最后创建了一个模块(play.api.inject.module) MessagesPersoModule.scala:

最后,在我的application.conf中使用它:

这真的有意义吗?我觉得写起来有点“复杂”。有没有更简单的方法用更少的代码/类来完成同样的逻辑

谢谢


Yoann

那么,在使用它之后,你的观察结果是什么?它是有效的,我仍然在使用它。但我仍然不确定这是正确的方法。你需要知道的一件事是(你,我指的是任何可能更新数据库的人)代码只会在0到30分钟(最大)内为你提供新的翻译因为我的演员是如何开始的。嗨,我也遇到了类似的问题。你改进了你的系统吗?你能把MessagesPersoModule的完整源代码发布到某个地方吗?这会非常有用。MessagesPersoModule的代码实际上是我使用的完整代码,除了导入和包信息。正如我在上一篇文章中所说的嗯,我现在对它的工作方式很满意,并且仍然按照这里写的方式使用它。如果我的需求发生变化,我可能会审查和重写代码,但现在还没有计划。谢谢。你的代码实际上帮了我很多忙。在你的提示下,我完成了我的模块,但采用了不同的单例方法。我不使用akka,但我调度了我的方法(loadCustomizedMessages,它调用我的服务从db获取消息)在ExtendedDefaultMessageAPI上获取db并在应用程序启动或消息更新时触发它。
class TranslationActor extends Actor {
  def receive = {
    case _ => {
      Logger.info("Starting to cache the translations")
      TranslationActor.tempCache = ListMap.empty
      var translations: ListMap[String, String] = ListMap.empty
      for (acceptedLanguage <- TranslationActor.acceptedLanguages) {
        val translationLanguageId: Long = TranslationLanguage.findByCode(acceptedLanguage).get.id
        val languageTranslations: Seq[Translation] = Translation.findAllByLanguageId(translationLanguageId)
        translations = new ListMap[String, String]
        for (languageTranslation <- languageTranslations) {
          val tag = EnglishTranslation.findById(languageTranslation.englishTranslationId).get.tag
          var returnedTranslation: String = languageTranslation.translation
          if (returnedTranslation.isEmpty) {
            returnedTranslation = tag
          }
          translations += tag -> new CacheValue(new Locale(acceptedLanguage), returnedTranslation).stringVar
        }
        TranslationActor.tempCache += acceptedLanguage -> translations
      }
      TranslationActor.cache = TranslationActor.tempCache
      Logger.info("Finished to cache the translations")
    }
  }
}

object TranslationActor {
  var acceptedLanguages: Seq[String] = Seq("fr", "en")
  var cache: ListMap[String, ListMap[String, String]] = ListMap.empty
  var tempCache: ListMap[String, ListMap[String, String]] = ListMap.empty
}

class CacheValue(locale: Locale, string: String) {
  val created: Long = System.currentTimeMillis
  var messageFormat: MessageFormat = null
  var localeVar: Locale = locale
  var stringVar: String = string

  def isOlderThan(period: Long): Boolean = {
    (System.currentTimeMillis - created) > (period * 1000)
  }

  def getMessageFormat: MessageFormat = {
    if (messageFormat == null) {
      if (stringVar != null) {
        messageFormat = new MessageFormat(stringVar, localeVar)
      } else {
        messageFormat = new MessageFormat("", localeVar)
      }
    }
    messageFormat
  }
}
@Singleton
class ManageTranslationDaemon @Inject() (actorSystem: ActorSystem, applicationLifecycle: ApplicationLifecycle) {
  Logger.info("Scheduling the translation daemon")
  val translationActor = actorSystem.actorOf(Props(new TranslationActor()))
  actorSystem.scheduler.schedule(1 seconds, 30 minutes, translationActor, "translationDaemon")

  applicationLifecycle.addStopHook { () =>
    Logger.info("Shutting down translation daemon")
    Future.successful(actorSystem.shutdown())
  }
}
class TranslationGuiceConfiguration extends AbstractModule {
  def configure() : Unit = {
    bind(classOf[ManageTranslationDaemon]).asEagerSingleton()
  }
}
class MessagesPersoApi @Inject() (environment: Environment, configuration: Configuration, langs: Langs) extends DefaultMessagesApi(environment: Environment, configuration: Configuration, langs: Langs) {

  private def joinPaths(first: Option[String], second: String) = first match {
    case Some(parent) => new java.io.File(parent, second).getPath
    case None => second
  }

  override protected def loadMessages(langCode: String): Map[String, String] = {
    TranslationActor.cache.getOrElse(langCode, loadMessagesFromFile("messages." + langCode))
  }

  protected def loadMessagesFromFile(langCode: String): Map[String, String] = {
    import scala.collection.JavaConverters._
    environment.classLoader.getResources(joinPaths(messagesPrefix, langCode)).asScala.toList
      .filterNot(url => Resources.isDirectory(environment.classLoader, url)).reverse
      .map { messageFile =>
        Messages.parse(Messages.UrlMessageSource(messageFile), messageFile.toString).fold(e => throw e, identity)
      }.foldLeft(Map.empty[String, String]) {_ ++ _}
  }

  override protected def loadAllMessages: Map[String, Map[String, String]] = {
    langs.availables.map(_.code).map { lang =>
      (lang, loadMessages(lang))
    }.toMap
      .+("default" -> loadMessagesFromFile("messages"))
      .+("default.play" -> loadMessagesFromFile("messages.default"))
  }
}
class MessagesPersoModule extends Module {
  def bindings(environment: Environment, configuration: Configuration) = {
    Seq(
      bind[Langs].to[DefaultLangs],
      bind[MessagesApi].to[MessagesPersoApi]
    )
  }
}
play.modules.disabled += "play.api.i18n.I18nModule"
play.modules.enabled += "modules.MessagesPersoModule"
play.modules.enabled += "modules.TranslationGuiceConfiguration"