Hello Stackoverflow community!
I have i18n working in my Play 2.4 application, so that's a good start.
What I want to do now is to override the implicit Lang sent to a template. For example, if the IP address is located in Sweden, I want to set the implicit Lang to Swedish regardless of what preferred language is set in the browser. How to do this?
This is my code:
My Application.scala controller:
package controllers
import javax.inject.Inject
import play.api.i18n.{I18nSupport, Lang, MessagesApi}
import play.api.mvc._
class Application #Inject() (val messagesApi: MessagesApi) extends Controller with I18nSupport {
def index = Action { implicit request =>
if (isIpAddressLocatedInSweden) {
implicit val langFromIp = Lang.apply("sv")
Logger.info("Language set to Swedish")
}
Ok(views.html.index())
}
private def isIpAddressLocatedInSweden: Boolean = {
[...]
}
}
My index.scala.html view:
#()(implicit messages: Messages, lang: Lang)
#main("Page Title") {
<span>#lang.toString()</span>
<h1>#Messages("home.title")</h1>
}
Unfortunately, the result is:
The <span> element contains the preferred browser language: "Lang(en,)"
The <h1> element contains the value I've written in messages.en
Thanks for any help!
I've done a little bit different:
I forced the result to the language in the query string.
It's a good form to keep the work and look for better approach later:
NOTE: I USED THE "MessagesApi" to make it happen.
package controllers
import play.api._
import play.api.mvc._
import play.api.i18n.I18nSupport
import play.api.i18n.Messages.Implicits._
import play.api.i18n.MessagesApi
import javax.inject.Inject
import play.api.i18n.Lang
import play.api.i18n._
class Application# Inject()(val messagesApi: MessagesApi) extends Controller with I18nSupport {
def index = Action {
implicit request =>
request.getQueryString("lang") match {
case Some(lang) => messagesApi.setLang(Ok(views.html.index()(messagesApi, Lang(lang))), Lang(lang))
case None => messagesApi.setLang(Ok(views.html.index()(messagesApi, Lang("en"))), Lang("en"))
}
}
}
conf/application.conf:
play.i18n.langs=["en","pt","fr"]
#()(implicit message: MessagesApi ,l: Lang)
<header>
<li>
<!-- change the language -->
<a href="./?lang=en">
<img src="#routes.Assets.versioned(" images/BR.png ")" />
</a>
</li>
<li>
<a href="./?lang=en">
<img src="#routes.Assets.versioned(" images/US.gif ")" />
</a>
</li>
<h1>#message("intro")</h1>
</header>
<p>#Html(message("description"))</p>
<p>#Html(message("description.education"))</p>
Conflicting implicits prevent exactly overriding Messages on Play 2.4 but I believe you can get what you were roughly looking for with the code below.
This is my code:
My conf/Application.conf:
# The application languages
# ~~~~~
play.i18n.langs = [ "en", "sv" ]
My conf/messages.sv
home.title=Svedish
My Application.scala controller:
package controllers
import javax.inject.Inject
import play.api.i18n.{I18nSupport, Lang, MessagesApi}
import play.api.mvc._
class IPMessages(lang: Lang, messages: MessagesApi) extends play.api.i18n.Messages(lang, messages){}
class Application #Inject() (val messagesApi: MessagesApi) extends Controller with I18nSupport {
def index = Action { implicit request =>
val langFromIp = if (isIpAddressLocatedInSweden) {
new Lang("sv")
} else {
request.acceptLanguages.head
}
implicit val ipMessages: IPMessages = new IPMessages(langFromIp, messagesApi)
Ok(views.html.index())
}
private def isIpAddressLocatedInSweden: Boolean = true
}
My index.scala.html view:
#()(implicit messages: IPMessages)
#main("Page Title") {
<span>#messages.toString()</span>
<h1>#messages("home.title")</h1>
}
This is the cleanest way I got it to work:
Code of my Application.scala controller:
class Application #Inject()(val messagesApi: MessagesApi) extends Controller with I18nSupport {
def index = Action { implicit request =>
val lang = if (isIpAddressLocatedInSweden) {
Lang.apply("sv")
} else {
Lang.preferred(request.acceptLanguages)
}
Ok(views.html.index()).withLang(lang)
}
private def isIpAddressLocatedInSweden: Boolean = {
[...]
}
}
The code of the index.scala.html view is unchanged.
Related
In my project I have this structure
app/
--> common/
--> DefyProductsComponents
--> DefyProductsLoader
--> controllers/
--> HomeController
DefyProductsComponents
package common
import com.softwaremill.macwire.wire
import controllers.{Assets, AssetsComponents, HomeController}
import play.api.ApplicationLoader.Context
import play.api.BuiltInComponentsFromContext
import play.api.routing.Router
import play.filters.HttpFiltersComponents
import router.Routes
import scala.concurrent.Future
class DefyProductsComponents(context: Context)
extends BuiltInComponentsFromContext(context)
with HttpFiltersComponents
with AssetsComponents {
// Controllers
lazy val homeController = wire[HomeController]
// Router
override lazy val assets = wire[Assets]
lazy val prefix: String = "/"
lazy val defyProductsRouter: Router = wire[Routes]
lazy val router: Router = defyProductsRouter
}
DefyProductsLoader
package common
import play.api._
import play.api.ApplicationLoader.Context
class DefyProductsLoader extends ApplicationLoader {
override def load(context: Context): Application = {
LoggerConfigurator(context.environment.classLoader).foreach { _.configure(context.environment) }
new DefyProductsComponents(context).application
}
}
HomeController
package controllers
import play.api.mvc._
class HomeController (val controllerComponents: ControllerComponents) extends BaseController {
def index() = Action { implicit request: Request[AnyContent] =>
Ok(views.html.index("Welcome to Play"))
}
}
I want to setup the test the tests, this is the test/ structure
test/
--> common/
--> DefyProductsServerTest
--> controllers
--> HomeControllerSpec
DefyProductsServerTest
package common
import org.scalatestplus.play.PlaySpec
import org.scalatestplus.play.components.OneAppPerTestWithComponents
import play.api.{BuiltInComponents, NoHttpFiltersComponents}
class DefyProductsServerTest extends PlaySpec with OneAppPerTestWithComponents {
override def components: BuiltInComponents = new DefyProductsComponents(context) with NoHttpFiltersComponents {
}
}
HomeControllerSpec
package controllers
import common.DefyProductsServerTest
import play.api.test._
import play.api.test.Helpers._
class HomeControllerSpec extends DefyProductsServerTest {
"HomeController GET" should {
"render the index page from the application" in {
val home = homeController.index().apply(FakeRequest(GET, "/"))
status(home) mustBe OK
contentType(home) mustBe Some("text/html")
contentAsString(home) must include ("Welcome to Play")
}
"render the index page from the router" in {
val request = FakeRequest(GET, "/")
val home = route(app, request).get
status(home) mustBe OK
contentType(home) mustBe Some("text/html")
contentAsString(home) must include ("Welcome to Play")
}
}
}
This setup I wrote is not working and I am not sure why in the HomeControllerSpec homeController is not found. I understand that in the tests all components from DefyProductsComponents are available and I am able to override
UPDATE: I created a very similar project in github. Just in case someone want to play with it https://github.com/agusgambina/play-scala-base.git
The easiest to do here is:
in DefyProductsServerTest, change components into:
override def components: DefyProductsComponents = ...
Then in your test you should be able to do:
val home = new components.homeController.index().apply(FakeRequest(GET, "/"))
I hate to ask - I really do but this one has got me for the moment..
I'm trying to compose some actions (in Play Framework & scala) with my main guide being this vid. However it was made a few years back so some of the functionality has since been deprecated and therefore I have had to find work-arounds as I go. Currently I am trying to output two asynchronous actions within some HTML markup.
I successfully outputted one action with this controller:
package controllers
import akka.actor.ActorSystem
import javax.inject._
import play.api.mvc._
import services.ServiceClient
import scala.concurrent.ExecutionContext
#Singleton
class AsyncController #Inject() (sc: ServiceClient)(actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends Controller {
def index = Action.async { request =>
val asy1 = sc.makeServiceCall("async1")
for {
async1Message <- asy1
} yield {
Ok(views.html.async1.async1(async1Message))
}
}
}
In case you are wondering the sc.makeServiceCall refers to this file:
class ServiceClient #Inject() (ws: WSClient) {
def makeServiceCall(serviceName: String): Future[String] = {
ws.url(s"http://localhost:9000/mock/$serviceName").get().map(_.body)
}
}
So I followed the video in its' guidance to compose two asynchronous actions with some HTML. And this is where it gets difficult/interesting/upsetting:
package controllers
import javax.inject.Inject
import akka.actor.ActorSystem
import play.api.mvc._
import scala.concurrent.{ExecutionContext}
import Ui.Pagelet
class AsyncHomeController #Inject() (as1: AsyncController)(as2: Async2Controller)(actorSystem: ActorSystem)(implicit exec: ExecutionContext) extends Controller {
def index = Action.async { request =>
val asy1 = as1.index(request)
val asy2 = as2.index(request)
for {
async1Result <- asy1
async2Result <- asy2
async1Body <- Pagelet.readBody(async1Result)
async2Body <- Pagelet.readBody(async2Result)
} yield {
Ok(views.html.home2(async1Body, async2Body))
}
}
}
So Async2Controller is very similar to AsyncController and Pagelet.readBody refers to this:
package Ui
import play.api.libs.iteratee.Iteratee
import play.api.mvc.{Codec, Result}
import play.twirl.api.Html
import scala.concurrent._
object Pagelet {
def readBody(result: Result)(implicit codec: Codec): Future[Html] = {
result.body.run(Iteratee.consume()).map(bytes => Html(new String(bytes, codec.charset)))
}
}
And this is wherein the error lies - which is:
value run is not a member of play.api.http.HttpEntity
I cannot find documentation on whether it needs to be injected or any indication that it has since been deprecated. If someone has got an answer to this or a work-around please divulge. Many thanks
The Iteratee lib is deprecated and was replaced by akka-stream. You need to change the implementation of readBody:
def readBody(result: Result)(implicit mat: Materializer, ec: ExecutionContext, codec: Codec): Future[Html] = {
result.body.consumeData.map(byteString => Html(codec.decode(byteString))
}
You also need to change the dependencies of the controller to get a Materializer:
class AsyncHomeController #Inject() (as1: AsyncController, as2: Async2Controller)(actorSystem: ActorSystem)(implicit exec: ExecutionContext, mat: Materializer)
Edit: code updated
I would like to cache a response of a controller while considering the language. With this page here on stackoverflow I was able to make almost everything right but I have one problem when I use the cache and click on a link on the webpage to change the language : it doesn't work directly : the language changes only after I refresh the page and it works perfectly when I try without the cache. The cacheHelper :
package controllers
import play.api.cache.Cached
import play.api.Configuration
import play.api.mvc._
import javax.inject.{ Inject, Singleton }
#Singleton
class CacheHelper #Inject() (val cached: Cached)(implicit configuration: Configuration) {
def apply(label: String, duration: Int)(action: EssentialAction) = {
cached({ r: RequestHeader => label + "." + getLanguage(r) }, duration) { action }
}
def getLanguage(request: RequestHeader): String = {
request.cookies.get("PLAY_LANG").map(_.value).getOrElse("fr")
}
}
the controller :
package controllers
import javax.inject._
import play.api.i18n._
import scala.concurrent.ExecutionContext
import play.api.mvc.{Action, Controller, EssentialAction, RequestHeader}
#Singleton
class ActuController #Inject() (val cacheHelper: CacheHelper, val messagesApi: MessagesApi)(implicit ec: ExecutionContext) extends Controller with I18nSupport {
def index = cacheHelper("homePage", 60) {
Action { implicit req =>
cacheHelper.getLanguage(req) match {
case "fr" => Ok("Bonjour le monde")
case "ru" => Ok("привет")
case _ => Ok("hello")
}
}
}
}
I have tried to set directly a "PLAY_LANG" cookie instead of using #withLang directly but it doesn't work either. Any ideas ? Thanks.
the "language changer" :
package controllers
import play.api.mvc._
import play.api.i18n.{ MessagesApi, Messages, Lang }
import javax.inject.{ Inject, Singleton }
import play.api.cache.Cached
#Singleton
class Application #Inject() (val messagesApi: MessagesApi) extends Controller {
def selectLang(lang: String) = Action { implicit request =>
request.headers.get(REFERER).map { referer =>
Redirect(referer).withLang(Lang(lang))
}.getOrElse {
Redirect(routes.Application.index).withLang(Lang(lang))
}
}
}
I am trying to migrate my app from 2.3 to 2.4.
In our code base we have used Messages extensively, so any way to remove that will also help.
Currently our code is like this
class MyController #Inject()
(val messagesApi: MessagesApi) extends Controller with I18nSupport{
def methodA() = {
new MyControllerService.doSomething()
}
}
class MyControllerService{
def doSomething()(implicit messages:Messages){
messages(any_key)
}
}
When compiling this code i am getting Error:(31, 84) Play 2 Compiler: could not find implicit value for parameter messages: play.api.i18n.Messages compile time error message.
Please help to resolve this issue, any suggestions to improve this kind of problem in better way is most welcome.
Do it in this way :
class MyController #Inject()
(val messagesApi: MessagesApi) extends Controller with I18nSupport{
def methodA() = { request =>
implicit val messages = messageApi.prefered(request)
new MyControllerService.doSomething()
}
}
class MyControllerService{
def doSomething()(implicit messages:Messages){
messages(any_key)
}
}
or you can pass created messages object directly in this way:
new MyControllerService.doSomething()(messages)
You need to have an implicit request in your Action and the imports for "inject" like so:
package controllers
import javax.inject.Inject
import javax.inject._
import play.api.i18n.{ I18nSupport, MessagesApi, Messages, Lang }
import play.api._
import play.api.mvc._
class Application #Inject() (val messagesApi: MessagesApi) extends Controller with I18nSupport {
def home() = Action { implicit request =>
Ok(views.html.home()).as(HTML)
}
}
in addition, you need to enable the injected router in your build.sbt:
routesGenerator := InjectedRoutesGenerator
How to add a extra field in todo application created using Play framework using Scala? I am using anorm DB... I am getting an error named "not found: value Task" in Application.scala at line 24. I have tried it out, please point out my mistake. Thanks in advance!
task.scala:
package models
import anorm._
import anorm.SqlParser._
import play.api.db._
import play.api.Play.current
case class Task(id: Long, label: String, name: String)
object Task {
val task = {
get[Long]("id") ~
get[String]("label") ~
get[String]("name") map {
case label~name => Task(id, name)
case id~label => Task(id, label)
}
}
def all(): List[Task] = DB.withConnection { implicit c =>
SQL("select * from task").as(task *)
}
def create(task: Task): Unit= {
DB.withConnection { implicit c =>
SQL("insert into task (label,name) values ({label},{name})").on(
'label -> label,
'name -> name
).executeUpdate()
}
}
def delete(id: Long) {
DB.withConnection { implicit c =>
SQL("delete from task where id = {id}").on(
'id -> id
).executeUpdate()
}
}
}
application.scala (controller class):
package controllers
import play.api._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import play.api.data.Form
import play.api.data.Forms.{tuple,nonEmptyText}
import play.api.mvc.{Action, Controller}
import anorm.NotAssigned
import models.Task
object Application extends Controller {
def index = Action {
Redirect(routes.Application.tasks)
}
val taskForm = Form(
tuple(
"label" -> nonEmptyText,
"name" -> nonEmptyText
)
)
def tasks = Action {
Ok(views.html.index(Task.all(), taskForm))
}
def showTask= Action {
Ok(views.html.test(Task.all(), taskForm))
}
def newTask = Action { implicit request =>
taskForm.bindFromRequest.fold(
errors => BadRequest(views.html.index(Task.all(), errors)),
{
case (label, name) => {
Task.create(Task(NotAssigned, label, name))
Redirect(routes.Application.showTask)
}
}
)
}
def deleteTask(id: Long) = Action {
Task.delete(id)
Redirect(routes.Application.showTask)
}
}
Index (view file):
#(tasks: List[Task], taskForm: Form[(String, String)])
#import helper._
<h2>Add a new task</h2>
#form(routes.Application.newTask) {
#inputText(taskForm("label"))
#inputText(taskForm("name"))
<input type="submit" value="Create">
}
test.html (view file 2):
#(tasks: List[Task], taskForm: Form[(String,String)])
#import helper._
#main("Todo list") {
<h1>#tasks.size task(s)</h1>
<ul>
#tasks.map { task =>
<li>
<b>#task.label</b>
<b>#task.name</b>
#form(routes.Application.deleteTask(task.id)) {
<input type="submit" value="Delete">
}
</li>
}
</ul>
}
Try to use :
(apply) and (unapply)
methods properly for form element.
(Task.apply)(Task.unapply)
import models.Task._ imports all the methods on the companion object models.Task into the current scope, not the Task class and object themselves. So the current code would allow you to just call all and it would refer to Task.all
Change the import to import models.Task to instead get Task into the scope in your Application object and you will be able to use the task methods like you are trying to.