scala Tutorial
Building an Expense Tracker with Scala 3 - Part 5: Internationalization & Testing
In this series
- •Building an Expense Tracker with Scala 3 - Part 1: Setup
- •Building an Expense Tracker with Scala 3 - Part 2: Database & Docker
- •Building an Expense Tracker with Scala 3 - Part 3: Authentication
- •Building an Expense Tracker with Scala 3 - Part 4: Expenses & Categories
- •Building an Expense Tracker with Scala 3 - Part 5: Internationalization & Testing
In this final part of the backend series, we will make our application accessible to a wider audience by adding Internationalization (I18n) support and ensure its reliability with Automated Tests.
Internationalization (I18n)
Internationalization allows your app to adapt to different languages and regions without changing the code. Play Framework has built-in support for this.
We already enabled it in conf/application.conf in Part 1:
play.i18n.langs = ["en", "es"]
Messages Files
Play looks for messages in conf/messages.xxx files. Let's create English and Spanish translations.
# conf/messages.en
hello.world = Hello World!
expense.tracker = Expense Tracker
# conf/messages.es
hello.world = ¡Hola Mundo!
expense.tracker = Rastreador de Gastos
Using Messages in Controllers
To use these messages, we inject MessagesApi or use the I18nSupport trait in our controller. The I18nSupport trait adds helper methods to look up messages based on the current request's Accept-Language header.
// app/controllers/HomeController.scala
package controllers
import play.api.mvc.*
import play.api.i18n.I18nSupport
class HomeController(cc: ControllerComponents) extends AbstractController(cc) with I18nSupport {
def index = Action { implicit request =>
// messagesApi("key") looks up the message.
// "implicit request" is needed so Play knows which language the user prefers.
Ok(messagesApi("hello.world"))
}
}
Now, if you request GET / with Accept-Language: es, you will see "¡Hola Mundo!". If you send Accept-Language: en (or nothing), you'll see "Hello World!".
Testing
Testing is crucial for any production application. It gives you confidence to refactor and deploy. Play uses ScalaTest by default, which is a powerful and flexible testing framework.
Unit Testing Controllers
Let's write a simple test for our HomeController. We'll use GuiceOneAppPerTest (even though we use Macwire, this helper is useful for integration tests) or simply instantiate the controller directly for unit tests.
Here is a unit test where we stub the components:
// test/controllers/HomeControllerSpec.scala
package controllers
import org.scalatestplus.play.*
import org.scalatestplus.play.guice.*
import play.api.test.*
import play.api.test.Helpers.*
class HomeControllerSpec extends PlaySpec with GuiceOneAppPerTest {
"HomeController GET" should {
"render the index page" in {
// We create a fake request
val request = FakeRequest(GET, "/")
// We instantiate the controller with stubbed components
val controller = new HomeController(stubControllerComponents())
val home = controller.index().apply(request)
// We assert the status and content
status(home) mustBe OK
contentAsString(home) must include ("Hello World!")
}
}
}
To run the tests, execute:
sbt test
Conclusion
Congratulations! You have built a complete Expense Tracker API with Scala 3. You've learned about:
- Project Setup & Macwire DI: Setting up a modern Scala environment.
- Database access: Using MySQL & Docker with JDBC.
- Authentication: Securing your API with JWT.
- CRUD operations: Managing Expenses and Categories.
- Internationalization & Testing: Polishing and verifying your app.
In the next series, we will build a modern React Frontend to interact with this API, bringing your backend to life!
