Repository: urlaubsverwaltung/urlaubsverwaltung (java)

Issue:
Fehlende Übersetzung in den Kalendern (iCal/Google)
**Describe the bug**
Übersetzung wird nicht verwendet, welche in der Personalisierung eingestellt ist.

**Expected behavior**
Es soll die korrekte Sprache verwendet werden für die Übersetzungen.



Pull request description:
5208 calendar absence translation
closes #5208

<!--

Thanks for contributing to the Urlaubsverwaltung.
Please review the following notes before submitting you pull request.

Please look for other issues or pull requests which already work on this topic. Is somebody already on it? Do you need to synchronize?

# Security Vulnerabilities

🛑 STOP! 🛑 If your contribution fixes a security vulnerability, please do not submit it.
Instead, please open a report of a security vulnerability via
[Report a security vulnerability](https://github.com/urlaubsverwaltung/urlaubsverwaltung/security/advisories/new)


# Describing Your Changes

If, having reviewed the notes above, you're ready to submit your pull request, please
provide a brief description of the proposed changes.

If they:
 🐞 fix a bug, please describe the broken behaviour and how the changes fix it.
    Please label with 'type: bug' and 'status: new'
    
 🎁 make an enhancement, please describe the new functionality and why you believe it's useful.
    Please label with 'type: enhancement' and 'status: new'
 
If your pull request relates to any existing issues,
please reference them by using the issue number prefixed with #.

-->


Patch:
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/absence/AbsenceType.java b/src/main/java/org/synyx/urlaubsverwaltung/absence/AbsenceType.java
deleted file mode 100644
index b156bc258..000000000
--- a/src/main/java/org/synyx/urlaubsverwaltung/absence/AbsenceType.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.synyx.urlaubsverwaltung.absence;
-
-public enum AbsenceType {
-
-    DEFAULT,
-    HOLIDAY_REPLACEMENT
-}
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/application/application/ApplicationMailService.java b/src/main/java/org/synyx/urlaubsverwaltung/application/application/ApplicationMailService.java
index 06b81f3fb..0cd82c455 100644
--- a/src/main/java/org/synyx/urlaubsverwaltung/application/application/ApplicationMailService.java
+++ b/src/main/java/org/synyx/urlaubsverwaltung/application/application/ApplicationMailService.java
@@ -5,10 +5,10 @@ import org.springframework.core.io.ByteArrayResource;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.synyx.urlaubsverwaltung.absence.AbsenceTimeConfiguration;
-import org.synyx.urlaubsverwaltung.absence.AbsenceType;
 import org.synyx.urlaubsverwaltung.absence.TimeSettings;
 import org.synyx.urlaubsverwaltung.application.comment.ApplicationComment;
 import org.synyx.urlaubsverwaltung.calendar.CalendarAbsence;
+import org.synyx.urlaubsverwaltung.calendar.CalendarAbsenceType;
 import org.synyx.urlaubsverwaltung.calendar.ICalService;
 import org.synyx.urlaubsverwaltung.calendar.ICalType;
 import org.synyx.urlaubsverwaltung.department.DepartmentService;
@@ -33,7 +33,7 @@ import static java.util.Comparator.comparing;
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.mapping;
 import static java.util.stream.Collectors.toList;
-import static org.synyx.urlaubsverwaltung.absence.AbsenceType.DEFAULT;
+import static org.synyx.urlaubsverwaltung.calendar.CalendarAbsenceType.DEFAULT;
 import static org.synyx.urlaubsverwaltung.calendar.ICalType.CANCELLED;
 import static org.synyx.urlaubsverwaltung.calendar.ICalType.PUBLISHED;
 import static org.synyx.urlaubsverwaltung.person.MailNotification.NOTIFICATION_EMAIL_APPLICATION_ALLOWED;
@@ -417,7 +417,7 @@ class ApplicationMailService {
     @Async
     void notifyHolidayReplacementAboutDirectlyAllowedApplication(HolidayReplacementEntity holidayReplacement, Application application) {
 
-        final ByteArrayResource calendarFile = generateCalendar(application, AbsenceType.HOLIDAY_REPLACEMENT, holidayReplacement.getPerson());
+        final ByteArrayResource calendarFile = generateCalendar(application, CalendarAbsenceType.HOLIDAY_REPLACEMENT, holidayReplacement.getPerson());
 
         final MailTemplateModelSupplier modelSupplier = locale -> Map.of(
             APPLICATION, application,
@@ -470,7 +470,7 @@ class ApplicationMailService {
     @Async
     void notifyHolidayReplacementAllow(HolidayReplacementEntity holidayReplacement, Application application) {
 
-        final ByteArrayResource calendarFile = generateCalendar(application, AbsenceType.HOLIDAY_REPLACEMENT, holidayReplacement.getPerson());
+        final ByteArrayResource calendarFile = generateCalendar(application, CalendarAbsenceType.HOLIDAY_REPLACEMENT, holidayReplacement.getPerson());
 
         final MailTemplateModelSupplier modelSupplier = locale -> Map.of(
             APPLICATION, application,
@@ -980,11 +980,11 @@ class ApplicationMailService {
         );
     }
 
-    private ByteArrayResource generateCalendar(Application application, AbsenceType absenceType, Person recipient) {
+    private ByteArrayResource generateCalendar(Application application, CalendarAbsenceType absenceType, Person recipient) {
         return generateCalendar(application, absenceType, PUBLISHED, recipient);
     }
 
-    private ByteArrayResource generateCalendar(Application application, AbsenceType absenceType, ICalType iCalType, Person recipient) {
+    private ByteArrayResource generateCalendar(Application application, CalendarAbsenceType absenceType, ICalType iCalType, Person recipient) {
         final CalendarAbsence absence = new CalendarAbsence(application.getPerson(), application.getPeriod(), getAbsenceTimeConfiguration(), absenceType);
         return iCalService.getSingleAppointment(absence, iCalType, recipient);
     }
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsence.java b/src/main/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsence.java
index c5d02a517..0d22b85c0 100644
--- a/src/main/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsence.java
+++ b/src/main/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsence.java
@@ -1,16 +1,14 @@
 package org.synyx.urlaubsverwaltung.calendar;
 
 import org.synyx.urlaubsverwaltung.absence.AbsenceTimeConfiguration;
-import org.synyx.urlaubsverwaltung.absence.AbsenceType;
 import org.synyx.urlaubsverwaltung.period.Period;
 import org.synyx.urlaubsverwaltung.person.Person;
 
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 
-import static java.lang.String.format;
-import static org.synyx.urlaubsverwaltung.absence.AbsenceType.DEFAULT;
-import static org.synyx.urlaubsverwaltung.absence.AbsenceType.HOLIDAY_REPLACEMENT;
+import static org.synyx.urlaubsverwaltung.calendar.CalendarAbsenceType.DEFAULT;
+import static org.synyx.urlaubsverwaltung.calendar.CalendarAbsenceType.HOLIDAY_REPLACEMENT;
 
 public class CalendarAbsence {
 
@@ -18,16 +16,16 @@ public class CalendarAbsence {
     private final ZonedDateTime endDate;
     private final Person person;
     private final boolean isAllDay;
-    private final AbsenceType absenceType;
+    private final CalendarAbsenceType calendarAbsenceType;
 
     public CalendarAbsence(Person person, Period period, AbsenceTimeConfiguration absenceTimeConfiguration) {
         this(person, period, absenceTimeConfiguration, DEFAULT);
     }
 
-    public CalendarAbsence(Person person, Period period, AbsenceTimeConfiguration absenceTimeConfiguration, AbsenceType absenceType) {
+    public CalendarAbsence(Person person, Period period, AbsenceTimeConfiguration absenceTimeConfiguration, CalendarAbsenceType calendarAbsenceType) {
 
         this.person = person;
-        this.absenceType = absenceType;
+        this.calendarAbsenceType = calendarAbsenceType;
 
         final ZonedDateTime periodStartDate = period.startDate().atStartOfDay(ZoneId.of(absenceTimeConfiguration.getTimeZoneId()));
         final ZonedDateTime periodEndDate = period.endDate().atStartOfDay(ZoneId.of(absenceTimeConfiguration.getTimeZoneId()));
@@ -69,15 +67,11 @@ public class CalendarAbsence {
     }
 
     public boolean isHolidayReplacement() {
-        return absenceType == HOLIDAY_REPLACEMENT;
+        return calendarAbsenceType == HOLIDAY_REPLACEMENT;
     }
 
-    public String getEventSubject() {
-        if (isHolidayReplacement()) {
-            return format("Vertretung für %s", person.getNiceName());
-        }
-
-        return format("%s abwesend", person.getNiceName());
+    public String getCalendarAbsenceTypeMessageKey() {
+        return calendarAbsenceType.getMessageKey();
     }
 
     @Override
@@ -87,7 +81,7 @@ public class CalendarAbsence {
             ", endDate=" + endDate +
             ", person=" + person +
             ", isAllDay=" + isAllDay +
-            ", absenceType=" + absenceType +
+            ", absenceType=" + calendarAbsenceType +
             '}';
     }
 }
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsenceType.java b/src/main/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsenceType.java
new file mode 100644
index 000000000..fe795773e
--- /dev/null
+++ b/src/main/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsenceType.java
@@ -0,0 +1,17 @@
+package org.synyx.urlaubsverwaltung.calendar;
+
+public enum CalendarAbsenceType {
+
+    DEFAULT("calendar.absence.type.default"),
+    HOLIDAY_REPLACEMENT("calendar.absence.type.holidayReplacement");
+
+    private final String messageKey;
+
+    CalendarAbsenceType(final String messageKey) {
+        this.messageKey = messageKey;
+    }
+
+    public String getMessageKey() {
+        return messageKey;
+    }
+}
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/calendar/ICalService.java b/src/main/java/org/synyx/urlaubsverwaltung/calendar/ICalService.java
index 4d73941d8..74087635f 100644
--- a/src/main/java/org/synyx/urlaubsverwaltung/calendar/ICalService.java
+++ b/src/main/java/org/synyx/urlaubsverwaltung/calendar/ICalService.java
@@ -15,15 +15,18 @@ import net.fortuna.ical4j.model.property.XProperty;
 import net.fortuna.ical4j.validate.ValidationException;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.MessageSource;
 import org.springframework.core.io.ByteArrayResource;
 import org.springframework.stereotype.Service;
 import org.synyx.urlaubsverwaltung.person.Person;
+import org.synyx.urlaubsverwaltung.user.UserSettingsService;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.net.URI;
 import java.time.ZonedDateTime;
 import java.util.List;
+import java.util.Locale;
 import java.util.Optional;
 
 import static net.fortuna.ical4j.model.parameter.Role.REQ_PARTICIPANT;
@@ -39,10 +42,18 @@ import static org.synyx.urlaubsverwaltung.calendar.ICalType.PUBLISHED;
 public class ICalService {
 
     private final CalendarProperties calendarProperties;
+    private final MessageSource messageSource;
+    private final UserSettingsService userSettingsService;
 
     @Autowired
-    ICalService(CalendarProperties calendarProperties) {
+    ICalService(
+        CalendarProperties calendarProperties,
+        MessageSource messageSource,
+        UserSettingsService userSettingsService
+    ) {
         this.calendarProperties = calendarProperties;
+        this.messageSource = messageSource;
+        this.userSettingsService = userSettingsService;
     }
 
     public ByteArrayResource getCalendar(String title, List<CalendarAbsence> absences, Person recipient) {
@@ -67,6 +78,9 @@ public class ICalService {
     }
 
     private Calendar prepareCalendar(List<CalendarAbsence> absences, ICalType method, Person recipient) {
+
+        final Locale locale = userSettingsService.getEffectiveLocale(List.of(recipient)).get(recipient);
+
         final Calendar calendar = new Calendar();
         calendar.add(VERSION_2_0);
         calendar.add(new ProdId("-//Urlaubsverwaltung//iCal4j 1.0//DE"));
@@ -78,7 +92,7 @@ public class ICalService {
         }
 
         absences.stream()
-            .map(absence -> this.toVEvent(absence, method, absence.getPerson().equals(recipient)))
+            .map(absence -> this.toVEvent(absence, method, absence.getPerson().equals(recipient), locale))
             .filter(Optional::isPresent)
             .map(Optional::get)
             .forEach(calendar::add);
@@ -86,21 +100,22 @@ public class ICalService {
         return calendar;
     }
 
-    private Optional<VEvent> toVEvent(CalendarAbsence absence, ICalType method, boolean isOwn) {
+    private Optional<VEvent> toVEvent(CalendarAbsence absence, ICalType method, boolean isOwn, Locale locale) {
 
         final ZonedDateTime startDateTime = absence.getStartDate();
         final ZonedDateTime endDateTime = absence.getEndDate();
+        final String summary = getTranslation(locale, absence.getCalendarAbsenceTypeMessageKey(), absence.getPerson().getNiceName());
 
         final VEvent event;
         if (absence.isAllDay()) {
             if (isSameDay(startDateTime, endDateTime)) {
-                event = new VEvent(startDateTime.toLocalDate(), absence.getEventSubject());
+                event = new VEvent(startDateTime.toLocalDate(), summary);
             } else {
-                event = new VEvent(startDateTime.toLocalDate(), endDateTime.toLocalDate(), absence.getEventSubject());
+                event = new VEvent(startDateTime.toLocalDate(), endDateTime.toLocalDate(), summary);
             }
             event.add(new XProperty("X-MICROSOFT-CDO-ALLDAYEVENT", "TRUE"));
         } else {
-            event = new VEvent(startDateTime.toInstant(), endDateTime.toInstant(), absence.getEventSubject());
+            event = new VEvent(startDateTime.toInstant(), endDateTime.toInstant(), summary);
         }
 
         event.add(new Uid(generateUid(absence)));
@@ -152,4 +167,8 @@ public class ICalService {
         }
         return byteArrayResource;
     }
+
+    private String getTranslation(Locale locale, String key, Object... args) {
+        return messageSource.getMessage(key, args, locale);
+    }
 }
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/calendarintegration/GoogleCalendarSyncProvider.java b/src/main/java/org/synyx/urlaubsverwaltung/calendarintegration/GoogleCalendarSyncProvider.java
index 324535418..66fc55b44 100644
--- a/src/main/java/org/synyx/urlaubsverwaltung/calendarintegration/GoogleCalendarSyncProvider.java
+++ b/src/main/java/org/synyx/urlaubsverwaltung/calendarintegration/GoogleCalendarSyncProvider.java
@@ -17,6 +17,7 @@ import java.util.Date;
 import java.util.List;
 import java.util.Optional;
 
+import static java.lang.String.format;
 import static java.lang.invoke.MethodHandles.lookup;
 import static org.apache.http.HttpStatus.SC_OK;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -136,7 +137,7 @@ public class GoogleCalendarSyncProvider implements CalendarProvider {
      */
     private static void fillEvent(CalendarAbsence absence, Event event) {
 
-        event.setSummary(absence.getEventSubject());
+        event.setSummary(getEventSubject(absence));
 
         final EventAttendee eventAttendee = new EventAttendee();
         eventAttendee.setEmail(absence.getPerson().getEmail());
@@ -168,4 +169,12 @@ public class GoogleCalendarSyncProvider implements CalendarProvider {
         event.setStart(startEventDateTime);
         event.setEnd(endEventDateTime);
     }
+
+    private static String getEventSubject(CalendarAbsence absence) {
+        if (absence.isHolidayReplacement()) {
+            return format("Vertretung für %s", absence.getPerson().getNiceName());
+        }
+
+        return format("%s abwesend", absence.getPerson().getNiceName());
+    }
 }
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index 84405469f..b78b3407a 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -499,6 +499,10 @@ absences.overview.month=Monat:
 absences.overview.department=Abteilung:
 absences.overview.department.help=Mehrfachauswahl mit Steuerungs- bzw. Befehlstaste
 
+# calendar absence types
+calendar.absence.type.default={0} abwesend
+calendar.absence.type.holidayReplacement=Vertretung für {0}
+
 # calendar
 calendar.person.title=Abwesenheitskalender von {0}
 calendar.department.title=Abwesenheitskalender der Abteilung {0}
diff --git a/src/main/resources/messages_de_AT.properties b/src/main/resources/messages_de_AT.properties
index 35de291a8..bd42ea013 100644
--- a/src/main/resources/messages_de_AT.properties
+++ b/src/main/resources/messages_de_AT.properties
@@ -488,6 +488,10 @@ absences.overview.month=Monat:
 absences.overview.department=Abteilung:
 absences.overview.department.help=Mehrfachauswahl mit Steuerungs- bzw. Befehlstaste
 
+# calendar absence types
+calendar.absence.type.default={0} abwesend
+calendar.absence.type.holidayReplacement=Vertretung für {0}
+
 # calendar
 calendar.person.title=Abwesenheitskalender von {0}
 calendar.department.title=Abwesenheitskalender der Abteilung {0}
diff --git a/src/main/resources/messages_el.properties b/src/main/resources/messages_el.properties
index 6f1d294af..fe4202443 100644
--- a/src/main/resources/messages_el.properties
+++ b/src/main/resources/messages_el.properties
@@ -452,6 +452,10 @@ absences.overview.month=Μήνας:
 absences.overview.department=Τμήμα:
 absences.overview.department.help=Πολλαπλή επιλογή με control ή command key
 
+# calendar absence types
+calendar.absence.type.default={0} απών/απούσα
+calendar.absence.type.holidayReplacement=Αντικατάσταση για {0}
+
 # calendar
 calendar.person.title=Ημερολόγιο αδειών του {0}
 calendar.department.title=Ημερολόγιο αδειών του τμήματος {0}
diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties
index c8a71976d..3f6504b20 100644
--- a/src/main/resources/messages_en.properties
+++ b/src/main/resources/messages_en.properties
@@ -493,6 +493,10 @@ absences.overview.month=Month:
 absences.overview.department=Department:
 absences.overview.department.help=Multiselection with control or command key
 
+# calendar absence types
+calendar.absence.type.default={0} absent
+calendar.absence.type.holidayReplacement=Replacement for {0}
+
 # calendar
 calendar.person.title=Absence calendar of {0}
 calendar.department.title=Absence calendar of the department {0}


Test patch:
diff --git a/src/test/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsenceTest.java b/src/test/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsenceTest.java
index 736a9790e..99ce1221b 100644
--- a/src/test/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsenceTest.java
+++ b/src/test/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsenceTest.java
@@ -15,7 +15,7 @@ import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.synyx.urlaubsverwaltung.absence.AbsenceType.HOLIDAY_REPLACEMENT;
+import static org.synyx.urlaubsverwaltung.calendar.CalendarAbsenceType.HOLIDAY_REPLACEMENT;
 
 class CalendarAbsenceTest {
 
@@ -129,23 +129,23 @@ class CalendarAbsenceTest {
     }
 
     @Test
-    void ensureCorrectEventSubject() {
+    void ensureCorrectMessageKeyForDefaultAbsence() {
 
         final LocalDate today = LocalDate.now(clock);
         final Period period = new Period(today, today, DayLength.FULL);
 
         final CalendarAbsence absence = new CalendarAbsence(person, period, getAbsenceTimeConfiguration());
-        assertThat(absence.getEventSubject()).isEqualTo("Marlene Muster abwesend");
+        assertThat(absence.getCalendarAbsenceTypeMessageKey()).isEqualTo("calendar.absence.type.default");
     }
 
     @Test
-    void ensureCorrectEventSubjectForHolidayReplacement() {
+    void ensureCorrectMessageKeyForHolidayReplacement() {
 
         final LocalDate today = LocalDate.now(clock);
         final Period period = new Period(today, today, DayLength.FULL);
 
         final CalendarAbsence absence = new CalendarAbsence(person, period, getAbsenceTimeConfiguration(), HOLIDAY_REPLACEMENT);
-        assertThat(absence.getEventSubject()).isEqualTo("Vertretung für Marlene Muster");
+        assertThat(absence.getCalendarAbsenceTypeMessageKey()).isEqualTo("calendar.absence.type.holidayReplacement");
     }
 
     @Test
diff --git a/src/test/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsenceTypeTest.java b/src/test/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsenceTypeTest.java
new file mode 100644
index 000000000..ec8a8759d
--- /dev/null
+++ b/src/test/java/org/synyx/urlaubsverwaltung/calendar/CalendarAbsenceTypeTest.java
@@ -0,0 +1,16 @@
+package org.synyx.urlaubsverwaltung.calendar;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.synyx.urlaubsverwaltung.calendar.CalendarAbsenceType.DEFAULT;
+import static org.synyx.urlaubsverwaltung.calendar.CalendarAbsenceType.HOLIDAY_REPLACEMENT;
+
+class CalendarAbsenceTypeTest {
+
+    @Test
+    void ensuresTheDefaultValuesOfTheMessageKeys() {
+        assertThat(DEFAULT.getMessageKey()).isEqualTo("calendar.absence.type.default");
+        assertThat(HOLIDAY_REPLACEMENT.getMessageKey()).isEqualTo("calendar.absence.type.holidayReplacement");
+    }
+}
diff --git a/src/test/java/org/synyx/urlaubsverwaltung/calendar/ICalServiceTest.java b/src/test/java/org/synyx/urlaubsverwaltung/calendar/ICalServiceTest.java
index dbe48255b..112c62bcd 100644
--- a/src/test/java/org/synyx/urlaubsverwaltung/calendar/ICalServiceTest.java
+++ b/src/test/java/org/synyx/urlaubsverwaltung/calendar/ICalServiceTest.java
@@ -2,31 +2,47 @@ package org.synyx.urlaubsverwaltung.calendar;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.context.MessageSource;
 import org.springframework.core.io.ByteArrayResource;
 import org.synyx.urlaubsverwaltung.absence.AbsenceTimeConfiguration;
-import org.synyx.urlaubsverwaltung.absence.AbsenceType;
 import org.synyx.urlaubsverwaltung.absence.TimeSettings;
 import org.synyx.urlaubsverwaltung.period.DayLength;
 import org.synyx.urlaubsverwaltung.period.Period;
 import org.synyx.urlaubsverwaltung.person.Person;
+import org.synyx.urlaubsverwaltung.user.UserSettingsService;
 
 import java.time.LocalDate;
 import java.util.List;
+import java.util.Map;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.time.format.DateTimeFormatter.ofPattern;
+import static java.util.Locale.GERMAN;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.synyx.urlaubsverwaltung.absence.AbsenceType.DEFAULT;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+import static org.synyx.urlaubsverwaltung.calendar.CalendarAbsenceType.DEFAULT;
 import static org.synyx.urlaubsverwaltung.calendar.ICalType.CANCELLED;
 import static org.synyx.urlaubsverwaltung.calendar.ICalType.PUBLISHED;
 import static org.synyx.urlaubsverwaltung.period.DayLength.FULL;
 import static org.synyx.urlaubsverwaltung.period.DayLength.MORNING;
 import static org.synyx.urlaubsverwaltung.period.DayLength.NOON;
 
+@ExtendWith(MockitoExtension.class)
 class ICalServiceTest {
 
     private ICalService sut;
 
+    @Mock
+    private MessageSource messageSource;
+    @Mock
+    private UserSettingsService userSettingsService;
+
+
     private static LocalDate toDateTime(String input) {
         return LocalDate.parse(input, ofPattern("yyyy-MM-dd"));
     }
@@ -35,13 +51,15 @@ class ICalServiceTest {
     void setUp() {
         final CalendarProperties calendarProperties = new CalendarProperties();
         calendarProperties.setOrganizer("no-reply@example.org");
-        sut = new ICalService(calendarProperties);
+        sut = new ICalService(calendarProperties, messageSource, userSettingsService);
     }
 
     @Test
     void ensureToGetCalendarForPersonAndNoAbsenceFound() {
 
-        final ByteArrayResource calendar = sut.getCalendar("Abwesenheitskalender", List.of(), null);
+        final Person recipient = new Person("muster", "Muster", "Marlene", "muster@example.org");
+
+        final ByteArrayResource calendar = sut.getCalendar("Abwesenheitskalender", List.of(), recipient);
         assertThat(convertCalendar(calendar))
             .isEqualToIgnoringNewLines("""
                 BEGIN:VCALENDAR
@@ -58,12 +76,13 @@ class ICalServiceTest {
     @Test
     void getCalendarForPersonForOneFullDay() {
 
-        final Person person = new Person("muster", "Muster", "Marlene", "muster@example.org");
-        person.setId(1L);
+        final Person recipient = new Person("muster", "Muster", "Marlene", "muster@example.org");
+        when(userSettingsService.getEffectiveLocale(List.of(recipient))).thenReturn(Map.of(recipient, GERMAN));
 
-        final CalendarAbsence fullDayAbsence = absence(person, toDateTime("2019-03-26"), toDateTime("2019-03-26"), FULL);
+        final CalendarAbsence fullDayAbsence = absence(recipient, toDateTime("2019-03-26"), toDateTime("2019-03-26"), FULL);
+        when(messageSource.getMessage(eq(fullDayAbsence.getCalendarAbsenceTypeMessageKey()), any(), eq(GERMAN))).thenReturn(recipient.getNiceName() + " abwesend");
 
-        final ByteArrayResource calendar = sut.getCalendar("Abwesenheitskalender", List.of(fullDayAbsence), person);
+        final ByteArrayResource calendar = sut.getCalendar("Abwesenheitskalender", List.of(fullDayAbsence), recipient);
         assertThat(convertCalendar(calendar))
             .isEqualToIgnoringNewLines("""
                 BEGIN:VCALENDAR
@@ -78,7 +97,7 @@ class ICalServiceTest {
                 DTSTART;VALUE=DATE:20190326
                 SUMMARY:Marlene Muster abwesend
                 X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
-                UID:F5C924EEB91550EBDD74CDA428649C8B
+                UID:806735982DE050F90FAEE972F010854E
                 ATTENDEE;ROLE=REQ-PARTICIPANT;CN=Marlene Muster:mailto:muster@example.org
                 \s
                 ORGANIZER:mailto:no-reply@example.org
@@ -90,12 +109,13 @@ class ICalServiceTest {
     @Test
     void getCalendarForPersonForHalfDayMorning() {
 
-        final Person person = new Person("muster", "Muster", "Marlene", "muster@example.org");
-        person.setId(1L);
+        final Person recipient = new Person("muster", "Muster", "Marlene", "muster@example.org");
+        when(userSettingsService.getEffectiveLocale(List.of(recipient))).thenReturn(Map.of(recipient, GERMAN));
 
-        final CalendarAbsence morningAbsence = absence(person, toDateTime("2019-04-26"), toDateTime("2019-04-26"), MORNING);
+        final CalendarAbsence morningAbsence = absence(recipient, toDateTime("2019-04-26"), toDateTime("2019-04-26"), MORNING);
+        when(messageSource.getMessage(eq(morningAbsence.getCalendarAbsenceTypeMessageKey()), any(), eq(GERMAN))).thenReturn(recipient.getNiceName() + " abwesend");
 
-        final ByteArrayResource calendar = sut.getCalendar("Abwesenheitskalender", List.of(morningAbsence), person);
+        final ByteArrayResource calendar = sut.getCalendar("Abwesenheitskalender", List.of(morningAbsence), recipient);
         assertThat(convertCalendar(calendar))
             .isEqualToIgnoringNewLines("""
                 BEGIN:VCALENDAR
@@ -110,7 +130,7 @@ class ICalServiceTest {
                 DTSTART:20190426T080000Z
                 DTEND:20190426T120000Z
                 SUMMARY:Marlene Muster abwesend
-                UID:45BCD3F64AD76CEF9040DF93047C41B3
+                UID:4F2F28B08279622C1D317D8D5F11D44D
                 ATTENDEE;ROLE=REQ-PARTICIPANT;CN=Marlene Muster:mailto:muster@example.org
                 \s
                 ORGANIZER:mailto:no-reply@example.org
@@ -122,12 +142,13 @@ class ICalServiceTest {
     @Test
     void getCalendarForPersonForMultipleFullDays() {
 
-        final Person person = new Person("muster", "Muster", "Marlene", "muster@example.org");
-        person.setId(1L);
+        final Person recipient = new Person("muster", "Muster", "Marlene", "muster@example.org");
+        when(userSettingsService.getEffectiveLocale(List.of(recipient))).thenReturn(Map.of(recipient, GERMAN));
 
-        final CalendarAbsence manyFullDayAbsence = absence(person, toDateTime("2019-03-26"), toDateTime("2019-04-01"), FULL);
+        final CalendarAbsence manyFullDayAbsence = absence(recipient, toDateTime("2019-03-26"), toDateTime("2019-04-01"), FULL);
+        when(messageSource.getMessage(eq(manyFullDayAbsence.getCalendarAbsenceTypeMessageKey()), any(), eq(GERMAN))).thenReturn(recipient.getNiceName() + " abwesend");
 
-        final ByteArrayResource calendar = sut.getCalendar("Abwesenheitskalender", List.of(manyFullDayAbsence), person);
+        final ByteArrayResource calendar = sut.getCalendar("Abwesenheitskalender", List.of(manyFullDayAbsence), recipient);
 
         assertThat(convertCalendar(calendar))
             .isEqualToIgnoringNewLines("""
@@ -144,7 +165,7 @@ class ICalServiceTest {
                 DTEND;VALUE=DATE:20190402
                 SUMMARY:Marlene Muster abwesend
                 X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
-                UID:D4C6D48B54BAED09F894FBDE0151381F
+                UID:322D5F265624AA63A4C508E8C363B29A
                 ATTENDEE;ROLE=REQ-PARTICIPANT;CN=Marlene Muster:mailto:muster@example.org
                 \s
                 ORGANIZER:mailto:no-reply@example.org
@@ -156,12 +177,13 @@ class ICalServiceTest {
     @Test
     void getCalendarForPersonForHalfDayNoon() {
 
-        final Person person = new Person("muster", "Muster", "Marlene", "muster@example.org");
-        person.setId(1L);
+        final Person recipient = new Person("muster", "Muster", "Marlene", "muster@example.org");
+        when(userSettingsService.getEffectiveLocale(List.of(recipient))).thenReturn(Map.of(recipient, GERMAN));
 
-        final CalendarAbsence noonAbsence = absence(person, toDateTime("2019-05-26"), toDateTime("2019-05-26"), NOON);
+        final CalendarAbsence noonAbsence = absence(recipient, toDateTime("2019-05-26"), toDateTime("2019-05-26"), NOON);
+        when(messageSource.getMessage(eq(noonAbsence.getCalendarAbsenceTypeMessageKey()), any(), eq(GERMAN))).thenReturn(recipient.getNiceName() + " abwesend");
 
-        final ByteArrayResource calendar = sut.getCalendar("Abwesenheitskalender", List.of(noonAbsence), person);
+        final ByteArrayResource calendar = sut.getCalendar("Abwesenheitskalender", List.of(noonAbsence), recipient);
         assertThat(convertCalendar(calendar))
             .isEqualToIgnoringNewLines("""
                 BEGIN:VCALENDAR
@@ -176,7 +198,7 @@ class ICalServiceTest {
                 DTSTART:20190526T120000Z
                 DTEND:20190526T160000Z
                 SUMMARY:Marlene Muster abwesend
-                UID:BB2267885BD8DF263E88D3062853E8A7
+                UID:497ED5D042F718878138A3E2F8C3C35C
                 ATTENDEE;ROLE=REQ-PARTICIPANT;CN=Marlene Muster:mailto:muster@example.org
                 \s
                 ORGANIZER:mailto:no-reply@example.org
@@ -188,12 +210,13 @@ class ICalServiceTest {
     @Test
     void getCalendarForPersonForHalfDayNoonWithEuropeBerlinTimezone() {
 
-        final Person person = new Person("muster", "Muster", "Marlene", "muster@example.org");
-        person.setId(1L);
+        final Person recipient = new Person("muster", "Muster", "Marlene", "muster@example.org");
+        when(userSettingsService.getEffectiveLocale(List.of(recipient))).thenReturn(Map.of(recipient, GERMAN));
 
-        final CalendarAbsence noonAbsence = absence(person, toDateTime("2019-05-26"), toDateTime("2019-05-26"), NOON, DEFAULT, "Europe/Berlin");
+        final CalendarAbsence noonAbsence = absence(recipient, toDateTime("2019-05-26"), toDateTime("2019-05-26"), NOON, DEFAULT, "Europe/Berlin");
+        when(messageSource.getMessage(eq(noonAbsence.getCalendarAbsenceTypeMessageKey()), any(), eq(GERMAN))).thenReturn(recipient.getNiceName() + " abwesend");
 
-        final ByteArrayResource calendar = sut.getCalendar("Abwesenheitskalender", List.of(noonAbsence), person);
+        final ByteArrayResource calendar = sut.getCalendar("Abwesenheitskalender", List.of(noonAbsence), recipient);
         assertThat(convertCalendar(calendar))
             .isEqualToIgnoringNewLines("""
                 BEGIN:VCALENDAR
@@ -208,42 +231,7 @@ class ICalServiceTest {
                 DTSTART:20190526T100000Z
                 DTEND:20190526T140000Z
                 SUMMARY:Marlene Muster abwesend
-                UID:203BCCB25ADDA1D45DAD353089374E99
-                ATTENDEE;ROLE=REQ-PARTICIPANT;CN=Marlene Muster:mailto:muster@example.org
-                \s
-                ORGANIZER:mailto:no-reply@example.org
-                END:VEVENT
-                END:VCALENDAR
-                """);
-    }
-
-    @Test
-    void getCalendarPublishEvent() {
-
-        final Person person = new Person("muster", "Muster", "Marlene", "muster@example.org");
-        person.setId(1L);
-
-        final CalendarAbsence noonAbsence = absence(person, toDateTime("2019-05-26"), toDateTime("2019-05-26"), NOON);
-
-        final CalendarProperties calendarProperties = new CalendarProperties();
-        calendarProperties.setOrganizer("no-reply@example.org");
-        final ICalService sut = new ICalService(calendarProperties);
-        final ByteArrayResource calendar = sut.getCalendar("Abwesenheitskalender", List.of(noonAbsence), person);
-        assertThat(convertCalendar(calendar))
-            .isEqualToIgnoringNewLines("""
-                BEGIN:VCALENDAR
-                VERSION:2.0
-                PRODID:-//Urlaubsverwaltung//iCal4j 1.0//DE
-                CALSCALE:GREGORIAN
-                X-MICROSOFT-CALSCALE:GREGORIAN
-                X-WR-CALNAME:Abwesenheitskalender
-                REFRESH-INTERVAL:P1D
-                BEGIN:VEVENT
-                DTSTAMP:<removedByConversionMethod>
-                DTSTART:20190526T120000Z
-                DTEND:20190526T160000Z
-                SUMMARY:Marlene Muster abwesend
-                UID:BB2267885BD8DF263E88D3062853E8A7
+                UID:791CAD0EA0808D42C4D2BED7D0A7CAC7
                 ATTENDEE;ROLE=REQ-PARTICIPANT;CN=Marlene Muster:mailto:muster@example.org
                 \s
                 ORGANIZER:mailto:no-reply@example.org
@@ -255,16 +243,13 @@ class ICalServiceTest {
     @Test
     void cancelSingleAppointment() {
 
-        final Person person = new Person("muster", "Muster", "Marlene", "muster@example.org");
-        person.setId(1L);
+        final Person recipient = new Person("muster", "Muster", "Marlene", "muster@example.org");
+        when(userSettingsService.getEffectiveLocale(List.of(recipient))).thenReturn(Map.of(recipient, GERMAN));
 
-        final CalendarAbsence noonAbsence = absence(person, toDateTime("2019-05-26"), toDateTime("2019-05-26"), NOON);
+        final CalendarAbsence noonAbsence = absence(recipient, toDateTime("2019-05-26"), toDateTime("2019-05-26"), NOON);
+        when(messageSource.getMessage(eq(noonAbsence.getCalendarAbsenceTypeMessageKey()), any(), eq(GERMAN))).thenReturn(recipient.getNiceName() + " abwesend");
 
-        final CalendarProperties calendarProperties = new CalendarProperties();
-        calendarProperties.setOrganizer("no-reply@example.org");
-        final ICalService sut = new ICalService(calendarProperties);
-
-        final ByteArrayResource calendar = sut.getSingleAppointment(noonAbsence, CANCELLED, person);
+        final ByteArrayResource calendar = sut.getSingleAppointment(noonAbsence, CANCELLED, recipient);
         assertThat(convertCalendar(calendar))
             .isEqualToIgnoringNewLines("""
                 BEGIN:VCALENDAR
@@ -278,7 +263,7 @@ class ICalServiceTest {
                 DTSTART:20190526T120000Z
                 DTEND:20190526T160000Z
                 SUMMARY:Marlene Muster abwesend
-                UID:BB2267885BD8DF263E88D3062853E8A7
+                UID:497ED5D042F718878138A3E2F8C3C35C
                 ATTENDEE;ROLE=REQ-PARTICIPANT;CN=Marlene Muster:mailto:muster@example.org
                 \s
                 SEQUENCE:1
@@ -291,15 +276,13 @@ class ICalServiceTest {
     @Test
     void singleAppointment() {
 
-        final Person person = new Person("muster", "Muster", "Marlene", "muster@example.org");
-        person.setId(1L);
-        final CalendarAbsence noonAbsence = absence(person, toDateTime("2019-05-26"), toDateTime("2019-05-26"), NOON);
+        final Person recipient = new Person("muster", "Muster", "Marlene", "muster@example.org");
+        when(userSettingsService.getEffectiveLocale(List.of(recipient))).thenReturn(Map.of(recipient, GERMAN));
 
-        final CalendarProperties calendarProperties = new CalendarProperties();
-        calendarProperties.setOrganizer("no-reply@example.org");
-        final ICalService sut = new ICalService(calendarProperties);
+        final CalendarAbsence noonAbsence = absence(recipient, toDateTime("2019-05-26"), toDateTime("2019-05-26"), NOON);
+        when(messageSource.getMessage(eq(noonAbsence.getCalendarAbsenceTypeMessageKey()), any(), eq(GERMAN))).thenReturn(recipient.getNiceName() + " abwesend");
 
-        final ByteArrayResource calendar = sut.getSingleAppointment(noonAbsence, PUBLISHED, person);
+        final ByteArrayResource calendar = sut.getSingleAppointment(noonAbsence, PUBLISHED, recipient);
         assertThat(convertCalendar(calendar))
             .isEqualToIgnoringNewLines("""
                 BEGIN:VCALENDAR
@@ -312,7 +295,7 @@ class ICalServiceTest {
                 DTSTART:20190526T120000Z
                 DTEND:20190526T160000Z
                 SUMMARY:Marlene Muster abwesend
-                UID:BB2267885BD8DF263E88D3062853E8A7
+                UID:497ED5D042F718878138A3E2F8C3C35C
                 ATTENDEE;ROLE=REQ-PARTICIPANT;CN=Marlene Muster:mailto:muster@example.org
                 \s
                 ORGANIZER:mailto:no-reply@example.org
@@ -325,16 +308,12 @@ class ICalServiceTest {
     void appointmentOfOtherPerson() {
 
         final Person person = new Person("muster", "Muster", "Marlene", "muster@example.org");
-        person.setId(1L);
 
         final Person recipient = new Person();
-        recipient.setId(2L);
+        when(userSettingsService.getEffectiveLocale(List.of(recipient))).thenReturn(Map.of(recipient, GERMAN));
 
         final CalendarAbsence absence = absence(person, toDateTime("2019-05-26"), toDateTime("2019-05-26"), FULL);
-
-        final CalendarProperties calendarProperties = new CalendarProperties();
-        calendarProperties.setOrganizer("no-reply@example.org");
-        final ICalService sut = new ICalService(calendarProperties);
+        when(messageSource.getMessage(eq(absence.getCalendarAbsenceTypeMessageKey()), any(), eq(GERMAN))).thenReturn(person.getNiceName() + " abwesend");
 
         final ByteArrayResource calendar = sut.getSingleAppointment(absence, PUBLISHED, recipient);
         assertThat(convertCalendar(calendar))
@@ -349,7 +328,7 @@ class ICalServiceTest {
                 DTSTART;VALUE=DATE:20190526
                 SUMMARY:Marlene Muster abwesend
                 X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
-                UID:22D8DC26F4271C049ED5601345B58D9C
+                UID:D2A4772AEB3FD20D5F6997FCD8F28719
                 ATTENDEE;ROLE=REQ-PARTICIPANT;CN=Marlene Muster:mailto:muster@example.org
                 \s
                 TRANSP:TRANSPARENT
@@ -362,17 +341,12 @@ class ICalServiceTest {
     @Test
     void appointmentOfOtherPersonWithoutEMailAddress() {
 
-        final Person person = new Person("muster", "Muster", "Marlene", null);
-        person.setId(1L);
-
         final Person recipient = new Person();
-        recipient.setId(2L);
+        when(userSettingsService.getEffectiveLocale(List.of(recipient))).thenReturn(Map.of(recipient, GERMAN));
 
+        final Person person = new Person("muster", "Muster", "Marlene", null);
         final CalendarAbsence absence = absence(person, toDateTime("2019-05-26"), toDateTime("2019-05-26"), FULL);
-
-        final CalendarProperties calendarProperties = new CalendarProperties();
-        calendarProperties.setOrganizer("no-reply@example.org");
-        final ICalService sut = new ICalService(calendarProperties);
+        when(messageSource.getMessage(eq(absence.getCalendarAbsenceTypeMessageKey()), any(), eq(GERMAN))).thenReturn(person.getNiceName() + " abwesend");
 
         final ByteArrayResource calendar = sut.getSingleAppointment(absence, PUBLISHED, recipient);
         assertThat(convertCalendar(calendar))
@@ -387,7 +361,7 @@ class ICalServiceTest {
                 DTSTART;VALUE=DATE:20190526
                 SUMMARY:Marlene Muster abwesend
                 X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
-                UID:22D8DC26F4271C049ED5601345B58D9C
+                UID:D2A4772AEB3FD20D5F6997FCD8F28719
                 TRANSP:TRANSPARENT
                 ORGANIZER:mailto:no-reply@example.org
                 END:VEVENT
@@ -398,15 +372,13 @@ class ICalServiceTest {
     @Test
     void holidayReplacement() {
 
-        final Person person = new Person("muster", "Muster", "Marlene", "muster@example.org");
+        final Person recipient = new Person("muster", "Muster", "Marlene", "muster@example.org");
+        when(userSettingsService.getEffectiveLocale(List.of(recipient))).thenReturn(Map.of(recipient, GERMAN));
 
-        final CalendarAbsence holidayReplacement = holidayReplacement(person, toDateTime("2019-05-26"), toDateTime("2019-05-26"), FULL);
-
-        final CalendarProperties calendarProperties = new CalendarProperties();
-        calendarProperties.setOrganizer("no-reply@example.org");
-        final ICalService sut = new ICalService(calendarProperties);
+        final CalendarAbsence holidayReplacement = holidayReplacement(recipient, toDateTime("2019-05-26"), toDateTime("2019-05-26"), FULL);
+        when(messageSource.getMessage(eq(holidayReplacement.getCalendarAbsenceTypeMessageKey()), any(), eq(GERMAN))).thenReturn("Vertretung für " + recipient.getNiceName());
 
-        final ByteArrayResource calendar = sut.getSingleAppointment(holidayReplacement, PUBLISHED, person);
+        final ByteArrayResource calendar = sut.getSingleAppointment(holidayReplacement, PUBLISHED, recipient);
         assertThat(convertCalendar(calendar))
             .isEqualToIgnoringNewLines("""
                 BEGIN:VCALENDAR
@@ -434,14 +406,14 @@ class ICalServiceTest {
     }
 
     private CalendarAbsence holidayReplacement(Person person, LocalDate start, LocalDate end, DayLength length) {
-        return absence(person, start, end, length, AbsenceType.HOLIDAY_REPLACEMENT);
+        return absence(person, start, end, length, CalendarAbsenceType.HOLIDAY_REPLACEMENT);
     }
 
-    private CalendarAbsence absence(Person person, LocalDate start, LocalDate end, DayLength length, AbsenceType absenceType) {
+    private CalendarAbsence absence(Person person, LocalDate start, LocalDate end, DayLength length, CalendarAbsenceType absenceType) {
         return absence(person, start, end, length, absenceType, "Etc/UTC");
     }
 
-    private CalendarAbsence absence(Person person, LocalDate start, LocalDate end, DayLength length, AbsenceType absenceType, String timeZoneId) {
+    private CalendarAbsence absence(Person person, LocalDate start, LocalDate end, DayLength length, CalendarAbsenceType absenceType, String timeZoneId) {
         final TimeSettings timeSettings = new TimeSettings();
         timeSettings.setTimeZoneId(timeZoneId);
         timeSettings.setWorkDayBeginHour(8);
diff --git a/src/test/java/org/synyx/urlaubsverwaltung/calendarintegration/CalendarSyncServiceTest.java b/src/test/java/org/synyx/urlaubsverwaltung/calendarintegration/CalendarSyncServiceTest.java
index af4cbd267..1a4694050 100644
--- a/src/test/java/org/synyx/urlaubsverwaltung/calendarintegration/CalendarSyncServiceTest.java
+++ b/src/test/java/org/synyx/urlaubsverwaltung/calendarintegration/CalendarSyncServiceTest.java
@@ -112,7 +112,7 @@ class CalendarSyncServiceTest {
         verify(googleCalendarSyncProvider).update(absenceArgumentCaptor.capture(), eq("eventId"), eq(calendarSettings));
         final CalendarAbsence absence = absenceArgumentCaptor.getValue();
         assertThat(absence.getPerson()).isEqualTo(person);
-        assertThat(absence.getEventSubject()).isEqualTo("first last abwesend");
+        assertThat(absence.getCalendarAbsenceTypeMessageKey()).isEqualTo("calendar.absence.type.default");
         assertThat(absence.getStartDate()).isEqualTo(ZonedDateTime.of(2022, 12, 10, 0, 0, 0, 0, ZoneId.of("Europe/Berlin")));
         assertThat(absence.getEndDate()).isEqualTo(ZonedDateTime.of(2022, 12, 13, 0, 0, 0, 0, ZoneId.of("Europe/Berlin")));
     }