Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ob 7109 add support for informal language fix #106

Merged
merged 7 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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_GERMAL_LANGUAGE_TAG = "de-DE-u-va-posix";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GERMAN :)


@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_GERMAL_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
Loading