Skip to content

Commit

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

Ob 7109 add support for informal language fix
  • Loading branch information
tkuzynow authored Jan 17, 2024
2 parents 56a26d2 + 82b45b7 commit 0870404
Show file tree
Hide file tree
Showing 14 changed files with 295 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.caritas.cob.mailservice.api;

import de.caritas.cob.mailservice.api.model.Dialect;
import de.caritas.cob.mailservice.api.service.TranslationService;
import java.util.Locale;
import lombok.NonNull;
Expand All @@ -13,16 +14,17 @@
@Service
@RequiredArgsConstructor
@Slf4j
public class RestApiMessageSource implements MessageSource {

public class TranslationMessageSource implements MessageSource {

public static final String VA_POSIX = "va-posix";
public final @NonNull TranslationService translationService;

@Override
public String getMessage(String code, Object[] args, String defaultMessage, Locale locale) {
log.info("getMessage called with code: {}, args: {}, defaultMessage: {}, locale: {}", code,
args, defaultMessage, locale);
return translationService.fetchTranslations(locale.getLanguage()).get(code);
Dialect dialect = determineDialect(locale);
return translationService.fetchTranslations(locale.getLanguage(), dialect).get(code);
}

@Override
Expand All @@ -40,4 +42,12 @@ public String getMessage(MessageSourceResolvable resolvable, Locale locale)
}
return getMessage(resolvable.getCodes()[0], null, locale);
}

private Dialect determineDialect(Locale locale) {
Dialect dialect = Dialect.FORMAL;
if ("DE".equals(locale.getCountry()) && VA_POSIX.equals(locale.getExtension('u'))) {
dialect = Dialect.INFORMAL;
}
return dialect;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class TranslationController {

@GetMapping(value = "/translations")
public ResponseEntity<Map<String, String>> getTranslations() {
var result = translationService.fetchTranslations("de");
var result = translationService.fetchTranslations("de", Dialect.FORMAL);
return new ResponseEntity<>(result, org.springframework.http.HttpStatus.OK);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.caritas.cob.mailservice.api.helper;

import de.caritas.cob.mailservice.api.model.Dialect;
import de.caritas.cob.mailservice.api.model.LanguageCode;
import java.util.Locale;
import java.util.Map;
Expand All @@ -13,6 +14,8 @@
@Component
public class ThymeleafHelper {

private static final String INFORMAL_GERMAN_LANGUAGE_TAG = "de-DE-u-va-posix";

@Autowired
private TemplateEngine tempTemplateEngine;

Expand All @@ -23,17 +26,23 @@ void init() {
templateEngine = tempTemplateEngine;
}

public static Optional<String> getProcessedHtml(Map<String, Object> data, LanguageCode languageCode, String templateName) {
public static Optional<String> getProcessedHtml(Map<String, Object> data, LanguageCode languageCode, String templateName, Dialect dialect) {

Context context = new Context();
Locale locale = Locale.forLanguageTag(languageCode.getValue());
Locale locale = Locale.forLanguageTag(getLanguageTag(languageCode, dialect));
context.setLocale(locale);

if (data != null) {
data.forEach(context::setVariable);
return Optional.of(templateEngine.process(templateName, context));
}
return Optional.empty();

}

private static String getLanguageTag(LanguageCode languageCode, Dialect dialect) {
if (languageCode == LanguageCode.DE && dialect == Dialect.INFORMAL) {
return INFORMAL_GERMAN_LANGUAGE_TAG;
}
return languageCode.getValue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package de.caritas.cob.mailservice.api.service;

import de.caritas.cob.mailservice.api.model.Dialect;
import de.caritas.cob.mailservice.config.apiclient.TranlationMangementServiceApiClient;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class DefaultTranslationsService {

public String fetchDefaultTranslations(String translationComponentName, String languageCode,
Dialect dialect) {
InputStream inputStream = tryFetchDefaultTranslationWithFallbackToEmptyDialect(translationComponentName, languageCode, dialect);
if (inputStream == null) {
return "{}";
}
try {
final List<String> fileLines = IOUtils
.readLines(inputStream, StandardCharsets.UTF_8.displayName());
return String.join("", fileLines);
} catch (IOException ex) {
throw new IllegalStateException(String.format(
"Json file with translations could not be loaded, translation component name: %s",
translationComponentName), ex);
}
}

private InputStream tryFetchDefaultTranslationWithFallbackToEmptyDialect(String translationComponentName, String languageCode,
Dialect dialect) {
InputStream inputStream = getInputStream(translationComponentName, languageCode,
dialect);
if (inputStream == null) {
log.warn(
"Default translations for component {}, language {}, dialect {} not found in resources. Will try to fallback to default translations for empty dialect.",
translationComponentName,
languageCode, dialect);

inputStream = getInputStream(translationComponentName, languageCode, null);
if (inputStream == null) {
log.warn(
"Default translations for component {}, language {} and empty dialect not found in resources. Returning empty translations.",
translationComponentName,
languageCode);
return null;
}
}
return inputStream;
}

private InputStream getInputStream(String translationComponentName, String languageCode,
Dialect dialect) {
String translationFilename = getTranslationFilename(
translationComponentName + "." + languageCode
+ TranlationMangementServiceApiClient.getDialectSuffix(dialect));
return TranslationService.class.getResourceAsStream(translationFilename);
}

private String getTranslationFilename(String templateName) {
return "/i18n/" + templateName.toLowerCase() + ".json";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ public Optional<String> render(TemplateDescription desc, MailDTO mail, Map<Strin

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

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

private boolean translationsArePresentAndNotEmpty(MailDTO mailDTO) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,17 @@
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;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.ResourceAccessException;

@Service
@Slf4j
Expand All @@ -35,15 +32,14 @@ public class TranslationService {
@Value("${weblate.component}")
private String component;

private final @NonNull TranlationMangementServiceApiClient tranlationMangementServiceApiClient;
private final @NonNull TranlationMangementServiceApiClient tranlationMangementServiceApiClient;

public TranslationService(TranlationMangementServiceApiClient tranlationMangementServiceApiClient) {
this.tranlationMangementServiceApiClient = tranlationMangementServiceApiClient;
}
private final @NonNull DefaultTranslationsService defaultTranslationsService;

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

@Cacheable(value = "translations")
Expand All @@ -63,7 +59,8 @@ public void evictCache() {
log.info("Evicting translations cache");
}

private Map<String, String> fetchTranslationAsMap(String languageCode, Dialect dialect) throws JsonProcessingException {
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);
Expand All @@ -76,47 +73,40 @@ public Optional<Map<String, String>> tryFetchTranslations(String languageCode, D
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);
log.warn("Error while processing json file with translations. Returning empty translations",
e);
return Optional.empty();
}
}

private String fetchTranslationsAsString(String languageCode, Dialect dialect) {
return fetchDefaultTranslationsFromTranslationsManagementSystem(languageCode, dialect);
}

private String fetchDefaultTranslationsFromTranslationsManagementSystem(String languageCode, Dialect dialect) {
try {
return tranlationMangementServiceApiClient.tryFetchTranslationsFromTranslationManagementService(project, component,
return tranlationMangementServiceApiClient.tryFetchTranslationsFromTranslationManagementService(
project, component,
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,
log.warn(
"Translations for component {}, language {} not found in weblate, returning default translations",
component,
languageCode);
return fetchDefaultTranslations(component, languageCode);
return defaultTranslationsService.fetchDefaultTranslations(component, languageCode,
dialect);
} else {
log.error("Error while fetching translations from translation management service", e);
throw e;
}
} catch (ResourceAccessException ex) {
log.error("ResourceAccessException error while fetching translations from translation management service. Will fallback to resolve default translations.");
log.debug("Exception details: ", ex);
return defaultTranslationsService.fetchDefaultTranslations(component, languageCode, dialect);
}
}

private String fetchDefaultTranslations(String translationComponentName, String languageCode) {
var inputStream = TranslationService.class.getResourceAsStream(
getTranslationFilename(translationComponentName + "." + languageCode));
if (inputStream == null) {
return "{}";
}
try {
final List<String> fileLines = IOUtils
.readLines(inputStream, StandardCharsets.UTF_8.displayName());
return String.join("", fileLines);
} catch (IOException ex) {
throw new IllegalStateException(String.format(
"Json file with translations could not be loaded, translation component name: %s",
translationComponentName), ex);
}
}

private String getTranslationFilename(String templateName) {
return "/i18n/" + templateName.toLowerCase() + ".json";
}

private class TranslationServiceException extends RuntimeException {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package de.caritas.cob.mailservice.config;

import de.caritas.cob.mailservice.api.RestApiMessageSource;
import de.caritas.cob.mailservice.api.TranslationMessageSource;
import de.caritas.cob.mailservice.api.service.TranslationService;
import java.nio.charset.StandardCharsets;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -56,7 +56,7 @@ private ITemplateResolver htmlClassLoaderTemplateResolver() {

@Bean
public MessageSource messageSource(TranslationService translationService) {
return new RestApiMessageSource(translationService);
return new TranslationMessageSource(translationService);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public String tryFetchTranslationsFromTranslationManagementService(String projec
return response.getBody();
}

private String getDialectSuffix(Dialect dialect) {
public static String getDialectSuffix(Dialect dialect) {
if (dialect == null) {
return StringUtils.EMPTY;
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/i18n/mailservice.en.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"mail.label.header": "Counseling & Help",
"mail.label.slogan": "Online. Anonym. Sicher.",
"mail.label.has.assigned.you.new.adviceseeker": "has assigned you {0} as new advice seeker.",
"mail.label.has.assigned.you.new.adviceseeker": "has assigned you {0} new advice seeker.",
"mail.label.has.given.you.new.adviceseeker": "has assigned you an advice seeker.",
"mail.label.dear": "Dear",
"mail.label.view.message": "View message",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ <h1 style='color: #FFFFFF; font-size: 26px; font-weight: bold; line-height: 26px
<div style="color: #3F373F; margin: 16px; font-size: 16px; line-height: 21px; font-family: 'Open Sans', 'OpenSans', 'Arial', 'sans-serif';">
<b>Liebe(r) <span data-th-text="${name}"></span>,</b><br/>
<br/>
Sie haben eine neue Nachricht in Ihren Beratungen.
<span data-th-text="#{mail.label.you.have.a.new.message.in.your.counselings}"/>
</div>
</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package de.caritas.cob.mailservice.api;

import static org.mockito.Mockito.verify;

import de.caritas.cob.mailservice.api.model.Dialect;
import de.caritas.cob.mailservice.api.service.TranslationService;
import java.util.Locale;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class TranslationMessageSourceTest {

private static final String INFORMAL_GERMAL_LANGUAGE_TAG = "de-DE-u-va-posix";

@InjectMocks
private TranslationMessageSource translationMessageSource;

@Mock
private TranslationService translationService;

@Test
void getMessage_Should_CallFetchTranslations_With_FormalDialect_For_DefaultLocale() {
// given
Locale locale = Locale.getDefault();

// when
translationMessageSource.getMessage("translation_key", new Object[]{}, "Message", locale);

// then
verify(translationService, Mockito.times(1)).fetchTranslations(locale.getLanguage(), Dialect.FORMAL);
}

@Test
void getMessage_Should_CallFetchTranslations_With_InformalDialect_For_ForLocaleForInformalGerman() {
// given
Locale locale = Locale.forLanguageTag(INFORMAL_GERMAL_LANGUAGE_TAG);

// when
translationMessageSource.getMessage("translation_key", new Object[]{}, "Message", locale);

// then
verify(translationService, Mockito.times(1)).fetchTranslations(locale.getLanguage(), Dialect.INFORMAL);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +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);
email.setDialect(Dialect.FORMAL);

var nameRecipient = new TemplateDataDTO()
.key("name_recipient")
Expand Down
Loading

0 comments on commit 0870404

Please sign in to comment.