Skip to content

Commit

Permalink
Merge pull request #105 from Onlineberatung/OB-7109-add-support-for-i…
Browse files Browse the repository at this point in the history
…nformal-language

Ob 7109 add support for informal language
  • Loading branch information
tkuzynow authored Jan 4, 2024
2 parents 1bdd4f9 + 32a6e05 commit 56a26d2
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 44 deletions.
9 changes: 9 additions & 0 deletions api/mailservice.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ components:
example: "[email protected]"
language:
$ref: '#/components/schemas/LanguageCode'
dialect:
$ref: '#/components/schemas/Dialect'
templateData:
type: array
items:
Expand Down Expand Up @@ -116,3 +118,10 @@ components:
so, sq, sr, ss, st, su, sv, sw, ta, te, tg, th, ti, tk, tl, tn, to, tr, ts, tt, tw,
ty, ug, uk, ur, uz, ve, vi, vo, wa, wo, xh, yi, yo, za, zh, zu
]

Dialect:
type: string
default: formal
enum: [
formal, informal
]
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package de.caritas.cob.mailservice.api.controller;

import de.caritas.cob.mailservice.api.model.Dialect;
import de.caritas.cob.mailservice.api.service.TranslationService;
import java.util.Map;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;


@Controller
@RestController
@RequiredArgsConstructor
public class TranslationController {

Expand All @@ -22,8 +25,12 @@ public ResponseEntity<Map<String, String>> getTranslations() {
return new ResponseEntity<>(result, org.springframework.http.HttpStatus.OK);
}

@GetMapping(value = "/translations/{dialect}")
public Map<String, String> getTranslations(@PathVariable Dialect dialect) {
return translationService.fetchTranslations("de", dialect);
}

@GetMapping(value = "/translations/evict")
@ResponseBody
public String evictTranslationCache() {
translationService.evictCache();
return "Cache evicted";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ private void loadRequiredMailDataAndSendMail(MailDTO mail, TemplateDescription d
}

private void renderAndSend(MailDTO mail, TemplateDescription desc, Map<String, Object> data) {
var subject = templateService.getRenderedSubject(desc, data, mail.getLanguage());
var subject = templateService.getRenderedSubject(desc, data, mail);
try {
templateService
.render(desc, mail.getTemplate(), data, mail.getLanguage())
.render(desc, mail, data)
.ifPresent(text -> sendHtmlMail(mail, desc, text, subject));
} catch (TemplateServiceException e) {
var message = String.format("Could not load template: %s", e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

import de.caritas.cob.mailservice.api.exception.TemplateServiceException;
import de.caritas.cob.mailservice.api.mailtemplate.TemplateDescription;
import de.caritas.cob.mailservice.api.model.Dialect;
import de.caritas.cob.mailservice.api.model.LanguageCode;
import de.caritas.cob.mailservice.api.model.MailDTO;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -39,41 +41,38 @@ public class TemplateService {
* Get the processed html template with replaced placeholders
*
* @param desc the template description
* @param name the template name
* @param data the template data
* @param language the template language
* @return if success, an optional with the html template, otherwise an empty optional
*/
public Optional<String> render(TemplateDescription desc, String name,
Map<String, Object> data, LanguageCode language) throws TemplateServiceException {
public Optional<String> render(TemplateDescription desc, MailDTO mail, Map<String, Object> data) throws TemplateServiceException {

data.put("urlimpressum", imprintUrl);
data.put("urldatenschutz", dataPrivacyUrl);

data.putAll(getTranslationMapAndDefaultToGermanIfNotFound(language));
data.putAll(getTranslationMapAndDefaultToGermanIfNotFound(mail.getLanguage(), mail.getDialect()));
List<String> missingFieldList = getMissingTemplateFields(desc, data);

if (!CollectionUtils.isEmpty(missingFieldList)) {
throw new TemplateServiceException(String.format(
"Mail request for template %s could not be executed due to missing fields for template processing. Missing fields: %s",
name, String.join(",", missingFieldList)));
mail.getTemplate(), String.join(",", missingFieldList)));
}

var templateFilename = desc.getTemplateFilenameOrFallback(language);
var templateFilename = desc.getTemplateFilenameOrFallback(mail.getLanguage());

return translationsArePresentAndNotEmpty(language) ? getProcessedHtml(data, language, templateFilename) :
return translationsArePresentAndNotEmpty(mail) ? getProcessedHtml(data, mail.getLanguage(), templateFilename) :
getProcessedHtml(data, LanguageCode.DE, templateFilename);
}

private boolean translationsArePresentAndNotEmpty(LanguageCode language) {
private boolean translationsArePresentAndNotEmpty(MailDTO mailDTO) {
var translations = translationService.tryFetchTranslations(
language.getValue());
mailDTO.getLanguage().getValue(), mailDTO.getDialect());
return translations.isPresent() && !translations.get().isEmpty();
}

private Map<String, String> getTranslationMapAndDefaultToGermanIfNotFound(LanguageCode language) {
return translationService.tryFetchTranslations(language.getValue()).orElse(
translationService.tryFetchTranslations(LanguageCode.DE.getValue())
private Map<String, String> getTranslationMapAndDefaultToGermanIfNotFound(LanguageCode language, Dialect dialect) {
return translationService.tryFetchTranslations(language.getValue(), dialect).orElse(
translationService.tryFetchTranslations(LanguageCode.DE.getValue(), dialect)
.orElse(new HashMap<>()));
}

Expand All @@ -85,23 +84,24 @@ private Map<String, String> getTranslationMapAndDefaultToGermanIfNotFound(Langua
* @return the subject with replaced placeholders
*/
public String getRenderedSubject(TemplateDescription templateDescription,
Map<String, Object> templateData, LanguageCode languageCode) {
Map<String, Object> templateData, MailDTO mailDTO) {
StringSubstitutor stringSubstitutor = new StringSubstitutor(templateData, "${", "}");
var subjectKey = templateDescription.getSubject().getKey();
if (subjectKey == null) {
log.warn("Subject key is null for template {}", templateDescription.getHtmlTemplateFilename().get(LanguageCode.DE));
return tryRenderDefaultSubject(stringSubstitutor);
}
return getRenderedSubjectForSubjectKeyNotNull(languageCode, stringSubstitutor, subjectKey);
return getRenderedSubjectForSubjectKeyNotNull(mailDTO, stringSubstitutor, subjectKey);
}

private String tryRenderDefaultSubject(StringSubstitutor stringSubstitutor) {
return stringSubstitutor.replace("${subject}");
}

private String getRenderedSubjectForSubjectKeyNotNull(LanguageCode languageCode, StringSubstitutor stringSubstitutor,
private String getRenderedSubjectForSubjectKeyNotNull(MailDTO mailDTO, StringSubstitutor stringSubstitutor,
String subjectKey) {
var translationKeys = getTranslationMapAndDefaultToGermanIfNotFound(languageCode);
var translationKeys = getTranslationMapAndDefaultToGermanIfNotFound(mailDTO.getLanguage(),
mailDTO.getDialect());
String subject = translationKeys.get(subjectKey);
if (subject != null) {
return stringSubstitutor.replace(subject);
Expand Down Expand Up @@ -133,5 +133,4 @@ private List<String> getMissingTemplateFields(TemplateDescription mailTemplate,
}
return missingFieldList;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.caritas.cob.mailservice.api.model.Dialect;
import de.caritas.cob.mailservice.config.apiclient.TranlationMangementServiceApiClient;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -42,9 +43,14 @@ public TranslationService(TranlationMangementServiceApiClient tranlationMangemen

@Cacheable(value = "translations")
public Map<String, String> fetchTranslations(String languageCode) {
return this.fetchTranslations(languageCode, Dialect.FORMAL);
}

@Cacheable(value = "translations")
public Map<String, String> fetchTranslations(String languageCode, Dialect dialect) {
try {

return fetchTranslationAsMap(languageCode);
return fetchTranslationAsMap(languageCode, dialect);
} catch (JsonProcessingException ex) {
throw new TranslationServiceException(String.format(
"Json file with translations could not be parsed, translation component name: %s",
Expand All @@ -57,28 +63,28 @@ public void evictCache() {
log.info("Evicting translations cache");
}

private Map<String, String> fetchTranslationAsMap(String languageCode) throws JsonProcessingException {
String translations = fetchTranslationsAsString(languageCode);
private Map<String, String> fetchTranslationAsMap(String languageCode, Dialect dialect) throws JsonProcessingException {
String translations = fetchTranslationsAsString(languageCode, dialect);
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(translations, Map.class);
}

@Cacheable(value = "optionalTranslations")
public Optional<Map<String, String>> tryFetchTranslations(String languageCode) {
public Optional<Map<String, String>> tryFetchTranslations(String languageCode, Dialect dialect) {

try {
var result = fetchTranslationAsMap(languageCode);
var result = fetchTranslationAsMap(languageCode, dialect);
return result.isEmpty() ? Optional.empty() : Optional.of(result);
} catch (JsonProcessingException e) {
log.warn("Error while processing json file with translations. Returning empty translations", e);
return Optional.empty();
}
}

private String fetchTranslationsAsString(String languageCode) {
private String fetchTranslationsAsString(String languageCode, Dialect dialect) {
try {
return tranlationMangementServiceApiClient.tryFetchTranslationsFromTranslationManagementService(project, component,
languageCode);
languageCode, dialect);
} catch (HttpClientErrorException e) {
if (HttpStatus.NOT_FOUND.equals(e.getStatusCode())) {
log.warn("Translations for component {}, language {} not found in weblate, returning default translations", component,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package de.caritas.cob.mailservice.config.apiclient;

import de.caritas.cob.mailservice.api.model.Dialect;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
Expand All @@ -27,15 +29,25 @@ public class TranlationMangementServiceApiClient {
private final @NonNull RestTemplate restTemplate;

public String tryFetchTranslationsFromTranslationManagementService(String project,
String component, String languageCode) {
String component, String languageCode, Dialect dialect) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Token " + apiKey);
String url = apiUrl + "translations/" + project + "/" + component + "/" + languageCode
String url = apiUrl + "translations/" + project + "/" + component + "/" + languageCode + getDialectSuffix(dialect)
+ "/file.json";

log.info("Calling url to fetch translations: {}", url);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET,
new HttpEntity<>(headers), String.class);
return response.getBody();
}

private String getDialectSuffix(Dialect dialect) {
if (dialect == null) {
return StringUtils.EMPTY;
}
if (dialect == Dialect.INFORMAL) {
return "@informal";
}
return StringUtils.EMPTY;
}
}
39 changes: 39 additions & 0 deletions src/main/resources/i18n/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"mail.label.header": "Beratung",
"mail.label.slogan": "Online. Anonym. Sicher.",
"mail.label.has.assigned.you.new.adviceseeker": "hat Dir {0} als neuen Ratsuchenden zugewiesen.",
"mail.label.has.given.you.new.adviceseeker": "hat Dir eine(n) Ratsuchende(n) übergeben.",
"mail.label.dear": "Liebe(r)",
"mail.label.view.message": "Nachricht ansehen",
"mail.label.best.regards": "Liebe Grüße",
"mail.label.your.team": "Dein Team von der Onlineberatung",
"mail.label.in.the.agency": "in der Beratungsstelle",
"mail.label.is.a.new.enquiry.received": "ist eine neue Erstanfrage eingegangen.",
"mail.label.view.enquiry": "Anfrage ansehen",
"mail.label.your.team.with.tenant": "Dein Team von ",
"mail.label.there.are.present": "Es liegen",
"mail.label.new.enquiries.in.agency": "Erstanfragen in der\n Beratungsstelle",
"mail.label.there.are.present.closure": "vor",
"mail.label.there.is.one.enquiry.in.agency.present": "es liegt eine Erstanfrage in der Beratungsstelle",
"mail.label.an.adviceseeker.has.registered.and.left.you.a.message": "ein(e) Ratsuchende(r) hat sich direkt bei Ihnen registriert und eine Nachricht hinterlassen.",
"mail.label.has.left.feedback.regarding.counselling.the.user": "hat eine Feedback-Nachricht für die Beratung von",
"mail.label.has.left.closure": "hinterlassen",
"mail.label.you.have.received.an.answear.from": "Du hast eine Antwort von",
"mail.label.you.have.received.an.answear.from.closure": "erhalten",
"mail.label.please.login.to.read.it": "Bitte meldest Du dich an, um Deine Antwort zu lesen!",
"mail.label.read.the.response": "Antwort lesen",
"mail.label.you.have.a.new.message.in.your.counselings" : "Du hast eine neue Nachricht in Deinen Beratungen",
"mail.label.your.consultant.wants.to.assign.you.to.new.consultant": "Dein(e) Berater(in) möchte Dir an eine(n) neue(n)\n Berater(in) übergeben. Dafür brauchen wir Deine Zustimmung.",
"mail.label.imprint": "Impressum",
"mail.label.privacy": "Datenschutz",
"mail.subject.assign.enquiry.notification": "Neue_r Ratsuchende_r",
"mail.subject.daily.enquiry.notification": "${subject}",
"mail.subject.enquiry.notification.consultant": "Neue Erstanfrage [PLZ: ${plz}]",
"mail.subject.feedback.message.notification": "Neue Feedback-Nachricht",
"mail.subject.free.text": "${subject}",
"mail.subject.message.notification.asker": "Antwort von ${consultantName}",
"mail.subject.message.notification.consultant": "Neue Nachricht [PLZ: ${plz}]",
"mail.subject.reassign.notification.consultant": "Neuzuweisung erfolgt",
"mail.subject.reassign.request.notification": "Anfrage Neuzuweisung",
"mail.subject.direct.enquiry.notification": "Neue_r Ratsuchende_r: Neue Erstanfrage über Kontaktlink [PLZ: ${plz}]"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.fasterxml.jackson.databind.ObjectMapper;
import de.caritas.cob.mailservice.api.model.Dialect;
import de.caritas.cob.mailservice.api.model.LanguageCode;
import de.caritas.cob.mailservice.api.model.MailDTO;
import de.caritas.cob.mailservice.api.model.MailsDTO;
Expand Down Expand Up @@ -67,7 +69,7 @@ class MailControllerE2EIT {
void setUp() {
Mockito.doThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)).when(
tranlationMangementServiceApiClient).tryFetchTranslationsFromTranslationManagementService(
anyString(), anyString(), anyString());
anyString(), anyString(), anyString(), any(Dialect.class));
}

@Test
Expand Down Expand Up @@ -235,6 +237,7 @@ private void givenAnEmailList(LanguageCode languageCode) {
var email = new MailDTO();
email.setEmail(RandomStringUtils.randomAlphanumeric(32));
email.setTemplate("reassign-confirmation-notification");
email.setDialect(Dialect.INFORMAL);

var nameRecipient = new TemplateDataDTO()
.key("name_recipient")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public void sendHtmlMails_Should_sendHtmlMails_When_MailHasAddressAndUsingExchan
ReflectionTestUtils.setField(mailService, FIELD_NAME_USE_SMTP, false);
when(templateDescriptionService.getTemplateDescription(any()))
.thenReturn(Optional.of(new TemplateDescription()));
when(templateService.render(any(), any(), any(), any()))
when(templateService.render(any(), any(), any()))
.thenReturn(Optional.of("success"));

MailsDTO mailsDTO = new MailsDTO().mails(singletonList(createMailDTO()));
Expand All @@ -121,7 +121,7 @@ public void sendHtmlMails_Should_sendHtmlMails_When_UsingSmtp()
ReflectionTestUtils.setField(mailService, FIELD_NAME_USE_SMTP, true);
when(templateDescriptionService.getTemplateDescription(any()))
.thenReturn(Optional.of(new TemplateDescription()));
when(templateService.render(any(), any(), any(), any()))
when(templateService.render(any(), any(), any()))
.thenReturn(Optional.of("success"));

MailsDTO mailsDTO = new MailsDTO().mails(singletonList(createMailDTO()));
Expand All @@ -147,7 +147,7 @@ public void sendHtmlMails_Should_sendSeveralHtmlMails_When_MoreThanOneMailIsInMa
ReflectionTestUtils.setField(mailService, FIELD_NAME_USE_SMTP, true);
when(templateDescriptionService.getTemplateDescription(any()))
.thenReturn(Optional.of(new TemplateDescription()));
when(templateService.render(any(), any(), any(), any()))
when(templateService.render(any(), any(), any()))
.thenReturn(Optional.of("success"));

MailsDTO mailsDTO = new MailsDTO()
Expand Down Expand Up @@ -178,7 +178,7 @@ public void sendHtmlMail_Should_ThrowInternalServerErrorExceptionAndLogException
ReflectionTestUtils.setField(mailService, FIELD_NAME_USE_SMTP, false);
when(templateDescriptionService.getTemplateDescription(any()))
.thenReturn(Optional.of(new TemplateDescription()));
when(templateService.render(any(), any(), any(), any()))
when(templateService.render(any(), any(), any()))
.thenReturn(Optional.of("success"));
ExchangeMailServiceException exception = mock(ExchangeMailServiceException.class);
doThrow(exception).when(exchangeMailService)
Expand All @@ -197,7 +197,7 @@ public void sendErrotMailDto_Should_sendUnescapedHtmlErrorMail() throws Exceptio
TemplateDescription templateDescription = new TemplateDescription();
when(templateDescriptionService.getTemplateDescription(any()))
.thenReturn(Optional.of(templateDescription));
when(templateService.render(any(), any(), any(), any()))
when(templateService.render(any(), any(), any()))
.thenReturn(Optional.of("success"));
ErrorMailDTO errorMailDTO = new ErrorMailDTO()
.template("free-text")
Expand All @@ -211,7 +211,7 @@ public void sendErrotMailDto_Should_sendUnescapedHtmlErrorMail() throws Exceptio
expectedData.put("text", "<h2>test</h2>");
verify(this.exchangeMailService, times(1)).prepareAndSendHtmlMail(any(), any(), any(), any());
verify(this.templateService, times(1)).getRenderedSubject(eq(templateDescription),
eq(expectedData), any(LanguageCode.class));
eq(expectedData), any(MailDTO.class));
}

@Test(expected = InternalServerErrorException.class)
Expand All @@ -220,7 +220,7 @@ public void sendErrotMailDto_Should_ThrowInternalServerErrorExceptionAndLogExcep
ReflectionTestUtils.setField(mailService, FIELD_NAME_USE_SMTP, false);
when(templateDescriptionService.getTemplateDescription(any()))
.thenReturn(Optional.of(new TemplateDescription()));
when(templateService.render(any(), any(), any(), any()))
when(templateService.render(any(), any(), any()))
.thenThrow(new TemplateServiceException(""));
ErrorMailDTO errorMailDTO = new ErrorMailDTO()
.template("free-text")
Expand Down
Loading

0 comments on commit 56a26d2

Please sign in to comment.