git » subscription-tool.git » commit 51039c6

added proper internationalization

author Thorsten Ortlepp
2026-01-31 01:26:07 UTC
committer Thorsten Ortlepp
2026-01-31 01:26:07 UTC
parent 2f12fef4772a581b58a616643d8aed5848ed75e4

added proper internationalization

src/main/java/dev/rubidium/subscriptiontool/SubscriptionToolApplication.java +1 -2
src/main/java/dev/rubidium/subscriptiontool/configuration/MessageSourceConfiguration.java +18 -0
src/main/java/dev/rubidium/subscriptiontool/controller/CustomErrorController.java +0 -8
src/main/java/dev/rubidium/subscriptiontool/controller/ManagementController.java +1 -7
src/main/java/dev/rubidium/subscriptiontool/controller/SubscriptionController.java +1 -11
src/main/java/dev/rubidium/subscriptiontool/properties/Translation.java +0 -37
src/main/java/dev/rubidium/subscriptiontool/scheduler/MailScheduler.java +9 -6
src/main/resources/messages.properties +38 -0
src/main/resources/templates/Confirm.html +4 -4
src/main/resources/templates/Error.html +4 -4
src/main/resources/templates/Index.html +4 -4
src/main/resources/templates/Manage.html +14 -14
src/main/resources/templates/SubscribeForm.html +5 -5
src/main/resources/templates/SubscribeSave.html +6 -6
src/main/resources/templates/UnsubscribeDelete.html +5 -5
src/main/resources/templates/UnsubscribeForm.html +5 -5
src/main/resources/translation.properties +0 -30

diff --git a/src/main/java/dev/rubidium/subscriptiontool/SubscriptionToolApplication.java b/src/main/java/dev/rubidium/subscriptiontool/SubscriptionToolApplication.java
index c782084..baaa928 100644
--- a/src/main/java/dev/rubidium/subscriptiontool/SubscriptionToolApplication.java
+++ b/src/main/java/dev/rubidium/subscriptiontool/SubscriptionToolApplication.java
@@ -1,13 +1,12 @@
 package dev.rubidium.subscriptiontool;
 
 import dev.rubidium.subscriptiontool.properties.MailProperties;
-import dev.rubidium.subscriptiontool.properties.Translation;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 
 @SpringBootApplication
-@EnableConfigurationProperties({Translation.class, MailProperties.class})
+@EnableConfigurationProperties({MailProperties.class})
 public class SubscriptionToolApplication {
 
   static void main(String[] args) {
diff --git a/src/main/java/dev/rubidium/subscriptiontool/configuration/MessageSourceConfiguration.java b/src/main/java/dev/rubidium/subscriptiontool/configuration/MessageSourceConfiguration.java
new file mode 100644
index 0000000..d48bce6
--- /dev/null
+++ b/src/main/java/dev/rubidium/subscriptiontool/configuration/MessageSourceConfiguration.java
@@ -0,0 +1,18 @@
+package dev.rubidium.subscriptiontool.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.ResourceBundleMessageSource;
+
+@Configuration
+public class MessageSourceConfiguration {
+
+  @Bean
+  public ResourceBundleMessageSource messageSource() {
+    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
+    messageSource.setBasename("messages");
+    messageSource.setDefaultEncoding("UTF-8");
+    messageSource.setUseCodeAsDefaultMessage(true);
+    return messageSource;
+  }
+}
diff --git a/src/main/java/dev/rubidium/subscriptiontool/controller/CustomErrorController.java b/src/main/java/dev/rubidium/subscriptiontool/controller/CustomErrorController.java
index e7a3a42..2ae005c 100644
--- a/src/main/java/dev/rubidium/subscriptiontool/controller/CustomErrorController.java
+++ b/src/main/java/dev/rubidium/subscriptiontool/controller/CustomErrorController.java
@@ -1,6 +1,5 @@
 package dev.rubidium.subscriptiontool.controller;
 
-import dev.rubidium.subscriptiontool.properties.Translation;
 import org.springframework.boot.webmvc.error.ErrorController;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
@@ -9,15 +8,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
 @Controller
 public class CustomErrorController implements ErrorController {
 
-  private final Translation translation;
-
-  public CustomErrorController(final Translation translation) {
-    this.translation = translation;
-  }
-
   @RequestMapping("/error")
   public String handleError(Model model) {
-    model.addAttribute("translation", translation);
     return "Error";
   }
 }
diff --git a/src/main/java/dev/rubidium/subscriptiontool/controller/ManagementController.java b/src/main/java/dev/rubidium/subscriptiontool/controller/ManagementController.java
index d952ea2..fb1600b 100644
--- a/src/main/java/dev/rubidium/subscriptiontool/controller/ManagementController.java
+++ b/src/main/java/dev/rubidium/subscriptiontool/controller/ManagementController.java
@@ -3,7 +3,6 @@ package dev.rubidium.subscriptiontool.controller;
 import dev.rubidium.subscriptiontool.model.Action;
 import dev.rubidium.subscriptiontool.model.ManagementAction;
 import dev.rubidium.subscriptiontool.model.Subscriber;
-import dev.rubidium.subscriptiontool.properties.Translation;
 import dev.rubidium.subscriptiontool.service.ManagementService;
 import java.util.ArrayList;
 import java.util.List;
@@ -21,19 +20,15 @@ public class ManagementController {
   private static final String ATTRIBUTE_NAME_MAILS = "mails";
 
   private final ManagementService managementService;
-  private final Translation translation;
 
-  public ManagementController(final ManagementService managementService,
-      final Translation translation) {
+  public ManagementController(final ManagementService managementService) {
     this.managementService = managementService;
-    this.translation = translation;
   }
 
   @GetMapping("/manage")
   public String index(Model model) {
     var subscribers = managementService.getSubscribers();
 
-    model.addAttribute(ATTRIBUTE_NAME_TRANSLATION, translation);
     model.addAttribute(ATTRIBUTE_NAME_SUBSCRIBERS, subscribers);
     model.addAttribute(ATTRIBUTE_NAME_MAILS, extractMails(subscribers));
 
@@ -50,7 +45,6 @@ public class ManagementController {
 
     var subscribers = managementService.getSubscribers();
 
-    model.addAttribute(ATTRIBUTE_NAME_TRANSLATION, translation);
     model.addAttribute(ATTRIBUTE_NAME_SUBSCRIBERS, subscribers);
     model.addAttribute(ATTRIBUTE_NAME_MAILS, extractMails(subscribers));
 
diff --git a/src/main/java/dev/rubidium/subscriptiontool/controller/SubscriptionController.java b/src/main/java/dev/rubidium/subscriptiontool/controller/SubscriptionController.java
index f6c5486..f59b554 100644
--- a/src/main/java/dev/rubidium/subscriptiontool/controller/SubscriptionController.java
+++ b/src/main/java/dev/rubidium/subscriptiontool/controller/SubscriptionController.java
@@ -1,7 +1,6 @@
 package dev.rubidium.subscriptiontool.controller;
 
 import dev.rubidium.subscriptiontool.model.Subscription;
-import dev.rubidium.subscriptiontool.properties.Translation;
 import dev.rubidium.subscriptiontool.service.SubscriptionService;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
@@ -19,24 +18,19 @@ public class SubscriptionController {
   private static final String ATTRIBUTE_NAME_EMAIL = "email";
 
   private final SubscriptionService subscriptionService;
-  private final Translation translation;
 
-  public SubscriptionController(final SubscriptionService subscriptionService,
-      final Translation translation) {
+  public SubscriptionController(final SubscriptionService subscriptionService) {
     this.subscriptionService = subscriptionService;
-    this.translation = translation;
   }
 
   @GetMapping("/")
   public String index(Model model) {
-    model.addAttribute(ATTRIBUTE_NAME_TRANSLATION, translation);
     return "Index";
   }
 
   @GetMapping("/subscribe")
   public String subscribeForm(Model model) {
     model.addAttribute(ATTRIBUTE_NAME_SUBSCRIPTION, new Subscription());
-    model.addAttribute(ATTRIBUTE_NAME_TRANSLATION, translation);
     return "SubscribeForm";
   }
 
@@ -47,7 +41,6 @@ public class SubscriptionController {
     if (!email.isEmpty()) {
       saved = subscriptionService.saveSubscription(email);
     }
-    model.addAttribute(ATTRIBUTE_NAME_TRANSLATION, translation);
     model.addAttribute(ATTRIBUTE_NAME_EMAIL, email);
     model.addAttribute(ATTRIBUTE_NAME_SUCCESS, saved);
     return "SubscribeSave";
@@ -56,7 +49,6 @@ public class SubscriptionController {
   @GetMapping("/unsubscribe")
   public String unsubscribeForm(Model model) {
     model.addAttribute(ATTRIBUTE_NAME_SUBSCRIPTION, new Subscription());
-    model.addAttribute(ATTRIBUTE_NAME_TRANSLATION, translation);
     return "UnsubscribeForm";
   }
 
@@ -67,7 +59,6 @@ public class SubscriptionController {
     if (!email.isEmpty()) {
       deleted = subscriptionService.deleteSubscription(email);
     }
-    model.addAttribute(ATTRIBUTE_NAME_TRANSLATION, translation);
     model.addAttribute(ATTRIBUTE_NAME_EMAIL, email);
     model.addAttribute(ATTRIBUTE_NAME_SUCCESS, deleted);
     return "UnsubscribeDelete";
@@ -80,7 +71,6 @@ public class SubscriptionController {
     if (!confirmationCode.isEmpty()) {
       confirmed = subscriptionService.confirmSubscription(confirmationCode);
     }
-    model.addAttribute(ATTRIBUTE_NAME_TRANSLATION, translation);
     model.addAttribute(ATTRIBUTE_NAME_SUCCESS, confirmed);
     return "Confirm";
   }
diff --git a/src/main/java/dev/rubidium/subscriptiontool/properties/Translation.java b/src/main/java/dev/rubidium/subscriptiontool/properties/Translation.java
deleted file mode 100644
index eca69dc..0000000
--- a/src/main/java/dev/rubidium/subscriptiontool/properties/Translation.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package dev.rubidium.subscriptiontool.properties;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-@ConfigurationProperties(prefix = "translation")
-public record Translation(String title,
-                          String subscribe,
-                          String subscribeText,
-                          String subscribeSuccess,
-                          String subscribeInstruction,
-                          String subscribeError,
-                          String unsubscribe,
-                          String unsubscribeText,
-                          String unsubscribeSuccess,
-                          String unsubscribeError,
-                          String confirmSuccess,
-                          String confirmError,
-                          String emailPlaceholder,
-                          String back,
-                          String mailSubject,
-                          String mailText,
-                          String error,
-                          String home,
-                          String manage,
-                          String tableMail,
-                          String tableStatus,
-                          String tableRegistered,
-                          String tableConfirmed,
-                          String tableDoiMail,
-                          String tableActions,
-                          String active,
-                          String inactive,
-                          String activate,
-                          String delete,
-                          String copy) {
-
-}
diff --git a/src/main/java/dev/rubidium/subscriptiontool/scheduler/MailScheduler.java b/src/main/java/dev/rubidium/subscriptiontool/scheduler/MailScheduler.java
index c46d590..456307a 100644
--- a/src/main/java/dev/rubidium/subscriptiontool/scheduler/MailScheduler.java
+++ b/src/main/java/dev/rubidium/subscriptiontool/scheduler/MailScheduler.java
@@ -1,11 +1,12 @@
 package dev.rubidium.subscriptiontool.scheduler;
 
 import dev.rubidium.subscriptiontool.properties.MailProperties;
-import dev.rubidium.subscriptiontool.properties.Translation;
 import dev.rubidium.subscriptiontool.service.MailService;
 import dev.rubidium.subscriptiontool.service.PersistenceService;
+import java.util.Locale;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.context.MessageSource;
 import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
@@ -18,15 +19,15 @@ public class MailScheduler {
 
   private final PersistenceService persistenceService;
   private final MailService mailService;
-  private final Translation translation;
+  private final MessageSource messageSource;
   private final MailProperties mailProperties;
 
   public MailScheduler(final PersistenceService persistenceService,
-      final MailService mailService, final Translation translation,
+      final MailService mailService, final MessageSource messageSource,
       final MailProperties mailProperties) {
     this.persistenceService = persistenceService;
     this.mailService = mailService;
-    this.translation = translation;
+    this.messageSource = messageSource;
     this.mailProperties = mailProperties;
   }
 
@@ -36,9 +37,11 @@ public class MailScheduler {
     persistenceService.getUnsentMails().forEach(mail -> {
       logger.info("Sending double opt-in mail for subscriber # {}", mail.id());
       String link = mailProperties.url() + mail.code();
-      String message = translation.mailText().formatted(link);
+      String message =
+          messageSource.getMessage("mail.body", null, Locale.getDefault()).formatted(link);
+      String subject = messageSource.getMessage("mail.subject", null, Locale.getDefault());
       if (mailService.sendMail(
-          mailProperties.from(), mail.email(), translation.mailSubject(), message)) {
+          mailProperties.from(), mail.email(), subject, message)) {
         persistenceService.updateMail(mail.id());
       }
     });
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
new file mode 100644
index 0000000..1063209
--- /dev/null
+++ b/src/main/resources/messages.properties
@@ -0,0 +1,38 @@
+title=Subscription Tool
+
+action.subscribe=Subscribe
+action.unsubscribe=Unsubscribe
+action.back=Back
+action.home=Home
+
+subscribe.text=Enter your email address below to subscribe:
+subscribe.success=Your email address {0} has been saved.
+subscribe.error=Error saving your email address {0}. Please try again later.
+subscribe.instruction=You have to confirm your subscription by clicking the link in the email we sent you.
+
+unsubscribe.text=Enter your email address below to unsubscribe:
+unsubscribe.success=Your email address {0} has been removed.
+unsubscribe.error=Error removing your email address {0}. Please try again later.
+
+confirm.success=Your subscription has been confirmed.
+confirm.error=Error confirming your subscription. Please try again later.
+
+manage.title=Management
+manage.column.mail=Mail
+manage.column.status=Status
+manage.column.registered=Registered
+manage.column.confirmed=Confirmed
+manage.column.doimail=DOI Mail
+manage.column.actions=Actions
+manage.status.active=Active
+manage.status.inactive=Inactive
+manage.action.activate=Activate
+manage.action.delete=Delete
+manage.action.copy=Copy to clipboard
+
+error.text=An error occurred
+
+placeholder.email=Your email address
+
+mail.subject=Please confirm your subscription
+mail.body=Hi,\n\nplease confirm your subscription by clicking the following link:\n%s\n\nBest regards
diff --git a/src/main/resources/templates/Confirm.html b/src/main/resources/templates/Confirm.html
index 64e7804..f96f5e6 100644
--- a/src/main/resources/templates/Confirm.html
+++ b/src/main/resources/templates/Confirm.html
@@ -4,15 +4,15 @@
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link href="/bootstrap.min.css" rel="stylesheet">
-  <title th:text="${translation.title()}"/>
+  <title th:text="#{title}"/>
 </head>
 <body>
 <div class="container-fluid">
-  <h1 class="text-center fs-3" th:text="${translation.title()}"/>
+  <h1 class="text-center fs-3" th:text="#{title}"/>
   <div class="row">
     <div class="col my-2 mx-4">
-      <p class="text-center text-success" th:if="${success == true}" th:text="${translation.confirmSuccess()}"/>
-      <p class="text-center text-danger" th:if="${success == false}" th:text="${translation.confirmError()}"/>
+      <p class="text-center text-success" th:if="${success == true}" th:text="#{confirm.success}"/>
+      <p class="text-center text-danger" th:if="${success == false}" th:text="#{confirm.error}"/>
     </div>
   </div>
 </div>
diff --git a/src/main/resources/templates/Error.html b/src/main/resources/templates/Error.html
index dc538e5..6e42a5a 100644
--- a/src/main/resources/templates/Error.html
+++ b/src/main/resources/templates/Error.html
@@ -4,15 +4,15 @@
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link href="/bootstrap.min.css" rel="stylesheet">
-  <title th:text="${translation.title()}"/>
+  <title th:text="#{title}"/>
 </head>
 <body>
 <div class="container-fluid">
-  <h1 class="text-center fs-3" th:text="${translation.title()}"/>
-  <h1 class="text-center fs-4 text-danger" th:text="${translation.error()}"/>
+  <h1 class="text-center fs-3" th:text="#{title}"/>
+  <h1 class="text-center fs-4 text-danger" th:text="#{error.text}"/>
   <div class="row">
     <div class="col my-3 text-center">
-      <a href="/" class="btn btn-primary" th:text="${translation.home()}"/>
+      <a href="/" class="btn btn-primary" th:text="#{action.home}"/>
     </div>
   </div>
 </div>
diff --git a/src/main/resources/templates/Index.html b/src/main/resources/templates/Index.html
index 7d9da7d..bbdef58 100644
--- a/src/main/resources/templates/Index.html
+++ b/src/main/resources/templates/Index.html
@@ -4,19 +4,19 @@
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <link href="/bootstrap.min.css" rel="stylesheet">
-    <title th:text="${translation.title()}"/>
+    <title th:text="#{title}"/>
   </head>
   <body>
     <div class="container-fluid">
-      <h1 class="text-center fs-3" th:text="${translation.title()}"/>
+      <h1 class="text-center fs-3" th:text="#{title}"/>
       <div class="row">
         <div class="col my-3 text-center">
-          <a href="/subscribe" class="btn btn-primary" th:text="${translation.subscribe()}"/>
+          <a href="/subscribe" class="btn btn-primary" th:text="#{action.subscribe}"/>
         </div>
       </div>
       <div class="row">
         <div class="col my-3 text-center">
-          <a href="/unsubscribe" class="btn btn-primary" th:text="${translation.unsubscribe()}"/>
+          <a href="/unsubscribe" class="btn btn-primary" th:text="#{action.unsubscribe}"/>
         </div>
       </div>
     </div>
diff --git a/src/main/resources/templates/Manage.html b/src/main/resources/templates/Manage.html
index dd27564..c5eb33f 100644
--- a/src/main/resources/templates/Manage.html
+++ b/src/main/resources/templates/Manage.html
@@ -4,7 +4,7 @@
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link href="/bootstrap.min.css" rel="stylesheet">
-  <title th:text="${translation.title()}"/>
+  <title th:text="#{title}"/>
   <script>
     function copyTo() {
       var text = document.getElementById("mailto").textContent;
@@ -14,25 +14,25 @@
 </head>
 <body>
 <div class="container-fluid">
-  <h1 class="text-center fs-3"><span th:text="${translation.title()}"/> <span th:text="${translation.manage()}"/></h1>
+  <h1 class="text-center fs-3"><span th:text="#{title}"/> <span th:text="#{manage.title}"/></h1>
 
   <table class="table table-striped">
     <thead>
       <tr>
-        <th scope="col" th:text="${translation.tableMail()}"/>
-        <th scope="col" th:text="${translation.tableStatus()}"/>
-        <th scope="col" th:text="${translation.tableRegistered()}"/>
-        <th scope="col" th:text="${translation.tableConfirmed()}"/>
-        <th scope="col" th:text="${translation.tableDoiMail()}"/>
-        <th scope="col" colspan="2" th:text="${translation.tableActions()}"/>
+        <th scope="col" th:text="#{manage.column.mail}"/>
+        <th scope="col" th:text="#{manage.column.status}"/>
+        <th scope="col" th:text="#{manage.column.registered}"/>
+        <th scope="col" th:text="#{manage.column.confirmed}"/>
+        <th scope="col" th:text="#{manage.column.doimail}"/>
+        <th scope="col" colspan="2" th:text="#{manage.column.actions}"/>
       </tr>
     </thead>
     <tbody>
       <tr th:each="subscriber : ${subscribers}">
         <td><span th:text="${subscriber.mail}"/></td>
 
-        <td th:if="${subscriber.confirmed == true}"><span th:text="${translation.active()}"/></td>
-        <td th:unless="${subscriber.confirmed == true}"><span th:text="${translation.inactive()}"/></td>
+        <td th:if="${subscriber.confirmed == true}"><span th:text="#{manage.status.active}"/></td>
+        <td th:unless="${subscriber.confirmed == true}"><span th:text="#{manage.status.inactive}"/></td>
 
         <td><span th:text="${#temporals.format(subscriber.registration)}"/></td>
 
@@ -43,18 +43,18 @@
         <td th:unless="${subscriber.doiMailTime() == null}"><span th:text="${#temporals.format(subscriber.doiMailTime())}"/></td>
 
         <td>
-          <button type="button" class="btn btn-secondary btn-sm" disabled th:if="${subscriber.confirmed == true}" th:text="${translation.activate()}"/>
+          <button type="button" class="btn btn-secondary btn-sm" disabled th:if="${subscriber.confirmed == true}" th:text="#{manage.status.active}"/>
           <form action="#" th:action="@{/manage}" method="post" th:unless="${subscriber.confirmed == true}">
             <input type="hidden" name="action" th:value="${T(dev.rubidium.subscriptiontool.model.Action).ACTIVATE}"/>
             <input type="hidden" name="subscription" th:value="${subscriber.id()}"/>
-            <input type="submit" class="btn btn-primary btn-sm" th:value="${translation.activate()}"/>
+            <input type="submit" class="btn btn-primary btn-sm" th:value="#{manage.action.activate}"/>
           </form>
         </td>
         <td>
           <form action="#" th:action="@{/manage}" method="post">
             <input type="hidden" name="action" th:value="${T(dev.rubidium.subscriptiontool.model.Action).DELETE}"/>
             <input type="hidden" name="subscription" th:value="${subscriber.id()}"/>
-            <input type="submit" class="btn btn-danger btn-sm" th:value="${translation.delete()}"/>
+            <input type="submit" class="btn btn-danger btn-sm" th:value="#{manage.action.delete}"/>
           </form>
         </td>
       </tr>
@@ -67,7 +67,7 @@
 
   <div class="py-3">
     <pre class="bg-secondary text-white text-wrap lh-lg" id="mailto" th:text="${mails}"/>
-    <p><button class="btn btn-primary btn-sm" onclick="copyTo()" th:text="${translation.copy()}"/></p>
+    <p><button class="btn btn-primary btn-sm" onclick="copyTo()" th:text="#{manage.action.copy}"/></p>
   </div>
 
 </div>
diff --git a/src/main/resources/templates/SubscribeForm.html b/src/main/resources/templates/SubscribeForm.html
index d1b77b9..5e5c0ac 100644
--- a/src/main/resources/templates/SubscribeForm.html
+++ b/src/main/resources/templates/SubscribeForm.html
@@ -4,21 +4,21 @@
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link href="/bootstrap.min.css" rel="stylesheet">
-  <title th:text="${translation.title()}"/>
+  <title th:text="#{title}"/>
 </head>
 <body>
 <div class="container-fluid">
-  <h1 class="text-center fs-3" th:text="${translation.title()}"/>
-  <p class="text-center" th:text="${translation.subscribeText()}"/>
+  <h1 class="text-center fs-3" th:text="#{title}"/>
+  <p class="text-center" th:text="#{subscribe.text}"/>
   <form action="#" th:action="@{/subscribe}" th:object="${subscription}" method="post">
     <div class="form-group row">
       <div class="col my-3 mx-4">
-        <input type="email" class="form-control" id="email" name="email" th:field="*{email}" th:placeholder="${translation.emailPlaceholder()}"/>
+        <input type="email" class="form-control" id="email" name="email" th:field="*{email}" th:placeholder="#{placeholder.email}"/>
       </div>
     </div>
     <div class="row">
       <div class="col my-3 text-center">
-        <input type="submit" class="btn btn-primary" th:value="${translation.subscribe()}"/>
+        <input type="submit" class="btn btn-primary" th:value="#{action.subscribe}"/>
       </div>
     </div>
   </form>
diff --git a/src/main/resources/templates/SubscribeSave.html b/src/main/resources/templates/SubscribeSave.html
index 3c7927d..9b1c831 100644
--- a/src/main/resources/templates/SubscribeSave.html
+++ b/src/main/resources/templates/SubscribeSave.html
@@ -4,25 +4,25 @@
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link href="/bootstrap.min.css" rel="stylesheet">
-  <title th:text="${translation.title()}"/>
+  <title th:text="#{title}"/>
 </head>
 <body>
 <div class="container-fluid">
-  <h1 class="text-center fs-3" th:text="${translation.title()}"/>
+  <h1 class="text-center fs-3" th:text="#{title}"/>
   <div class="row">
     <div class="col my-2 mx-4">
-      <p class="text-center text-success" th:if="${success == true}" th:text="${T(java.lang.String).format(translation.subscribeSuccess(),email)}"/>
-      <p class="text-center text-danger" th:if="${success == false}" th:text="${T(java.lang.String).format(translation.subscribeError(),email)}"/>
+      <p class="text-center text-success" th:if="${success == true}" th:text="#{subscribe.success(${email})}"/>
+      <p class="text-center text-danger" th:if="${success == false}" th:text="#{subscribe.error(${email})}"/>
     </div>
   </div>
   <div class="row" th:if="${success == true}">
     <div class="col my-2 mx-4">
-      <p class="text-center" th:text="${translation.subscribeInstruction()}"/>
+      <p class="text-center" th:text="#{subscribe.instruction}"/>
     </div>
   </div>
   <div class="row">
     <div class="col my-2 mx-4 text-center">
-      <a href="/subscribe" class="btn btn-primary" th:text="${translation.back()}"/>
+      <a href="/subscribe" class="btn btn-primary" th:text="#{action.back}"/>
     </div>
   </div>
 </div>
diff --git a/src/main/resources/templates/UnsubscribeDelete.html b/src/main/resources/templates/UnsubscribeDelete.html
index 12af6d7..8286b09 100644
--- a/src/main/resources/templates/UnsubscribeDelete.html
+++ b/src/main/resources/templates/UnsubscribeDelete.html
@@ -4,20 +4,20 @@
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link href="/bootstrap.min.css" rel="stylesheet">
-  <title th:text="${translation.title()}"/>
+  <title th:text="#{title}"/>
 </head>
 <body>
 <div class="container-fluid">
-  <h1 class="text-center fs-3" th:text="${translation.title()}"/>
+  <h1 class="text-center fs-3" th:text="#{title}"/>
   <div class="row">
     <div class="col my-2 mx-4">
-      <p class="text-center text-success" th:if="${success == true}" th:text="${T(java.lang.String).format(translation.unsubscribeSuccess(),email)}"/>
-      <p class="text-center text-danger" th:if="${success == false}" th:text="${T(java.lang.String).format(translation.unsubscribeError(),email)}"/>
+      <p class="text-center text-success" th:if="${success == true}" th:text="#{unsubscribe.success(${email})}"/>
+      <p class="text-center text-danger" th:if="${success == false}" th:text="#{unsubscribe.error(${email})}"/>
     </div>
   </div>
   <div class="row">
     <div class="col my-2 mx-4 text-center">
-      <a href="/unsubscribe" class="btn btn-primary" th:text="${translation.back()}"/>
+      <a href="/unsubscribe" class="btn btn-primary" th:text="#{action.back}"/>
     </div>
   </div>
 </div>
diff --git a/src/main/resources/templates/UnsubscribeForm.html b/src/main/resources/templates/UnsubscribeForm.html
index 8eca8fc..d342f23 100644
--- a/src/main/resources/templates/UnsubscribeForm.html
+++ b/src/main/resources/templates/UnsubscribeForm.html
@@ -4,21 +4,21 @@
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link href="/bootstrap.min.css" rel="stylesheet">
-  <title th:text="${translation.title()}"/>
+  <title th:text="#{title}"/>
 </head>
 <body>
 <div class="container-fluid">
-  <h1 class="text-center fs-3" th:text="${translation.title()}"/>
-  <p class="text-center" th:text="${translation.unsubscribeText()}"/>
+  <h1 class="text-center fs-3" th:text="#{title}"/>
+  <p class="text-center" th:text="#{unsubscribe.text}"/>
   <form action="#" th:action="@{/unsubscribe}" th:object="${subscription}" method="post">
     <div class="form-group row">
       <div class="col my-3 mx-4">
-        <input type="email" class="form-control" id="email" name="email" th:field="*{email}" th:placeholder="${translation.emailPlaceholder()}"/>
+        <input type="email" class="form-control" id="email" name="email" th:field="*{email}" th:placeholder="#{placeholder.email}"/>
       </div>
     </div>
     <div class="row">
       <div class="col my-3 text-center">
-        <input type="submit" class="btn btn-primary" th:value="${translation.unsubscribe()}"/>
+        <input type="submit" class="btn btn-primary" th:value="#{action.unsubscribe}"/>
       </div>
     </div>
   </form>
diff --git a/src/main/resources/translation.properties b/src/main/resources/translation.properties
deleted file mode 100644
index f9acb32..0000000
--- a/src/main/resources/translation.properties
+++ /dev/null
@@ -1,30 +0,0 @@
-translation.title=My App
-translation.subscribe=Subscribe
-translation.subscribeText=This text explains the subscription:
-translation.subscribeSuccess=Your email address %s has been saved.
-translation.subscribeInstruction=You have to confirm your subscription by clicking the link in the email we sent you.
-translation.subscribeError=Error saving your email address %s. Please try again later.
-translation.unsubscribe=Unsubscribe
-translation.unsubscribeText=This text explains the unsubscription:
-translation.unsubscribeSuccess=Your email address %s has been removed.
-translation.unsubscribeError=Error removing your email address %s. Please try again later.
-translation.confirmSuccess=Your subscription has been confirmed.
-translation.confirmError=Error confirming your subscription. Please try again later.
-translation.emailPlaceholder=Your email address
-translation.back=Back
-translation.mailSubject=Please confirm your subscription
-translation.mailText=Hi,\n\nplease confirm your subscription by clicking the following link:\n%s\n\nBest regards
-translation.error=An error occurred
-translation.home=Home
-translation.manage=Management
-translation.tableMail=Mail
-translation.tableStatus=Status
-translation.tableRegistered=Registered
-translation.tableConfirmed=Confirmed
-translation.tableDoiMail=DOI Mail
-translation.tableActions=Actions
-translation.active=active
-translation.inactive=inactive
-translation.activate=activate
-translation.delete=delete
-translation.copy=copy to clipboard