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

Add support for multiple languages #84

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package se.citerus.dddsample.infrastructure.i18n;

import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.SimpleLocaleContext;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.i18n.AbstractLocaleContextResolver;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;

public class QueryParamLocaleResolver extends AbstractLocaleContextResolver {
private List<Locale> supportedLocales;

@Override
public LocaleContext resolveLocaleContext(HttpServletRequest request) {
String[] locales = request.getParameterMap().get("lang");
String localeName;
if (locales != null && locales.length > 0) {
localeName = locales[0];
} else {
return new SimpleLocaleContext(getDefaultLocale());
}

Locale requestLocale = convertLocaleName(localeName);
List<Locale> supportedLocales = getSupportedLocales();
if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
return new SimpleLocaleContext(requestLocale);
}

Locale supportedLocale = findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return new SimpleLocaleContext(supportedLocale);
}
return new SimpleLocaleContext(getDefaultLocale() != null ? getDefaultLocale() : requestLocale);
}

private Locale findSupportedLocale(HttpServletRequest request, List<Locale> supportedLocales) {
Enumeration<Locale> requestLocales = request.getLocales();
Locale languageMatch = null;
while (requestLocales.hasMoreElements()) {
Locale locale = requestLocales.nextElement();
if (supportedLocales.contains(locale)) {
if (languageMatch == null || languageMatch.getLanguage().equals(locale.getLanguage())) {
// Full match: language + country, possibly narrowed from earlier language-only match
return locale;
}
}
else if (languageMatch == null) {
// Let's try to find a language-only match as a fallback
for (Locale candidate : supportedLocales) {
if (!StringUtils.hasLength(candidate.getCountry()) &&
candidate.getLanguage().equals(locale.getLanguage())) {
languageMatch = candidate;
break;
}
}
}
}
return languageMatch;
}

private Locale convertLocaleName(String localeName) {
return localeName.contains("_") ? new Locale(localeName.split("_")[0], localeName.split("_")[1]) : new Locale(localeName);
}

@Override
public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
// intentionally left blank
}

public List<Locale> getSupportedLocales() {
return supportedLocales;
}

public void setSupportedLocales(List<Locale> supportedLocales) {
this.supportedLocales = supportedLocales;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@
import org.springframework.lang.Nullable;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.FixedLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import se.citerus.dddsample.application.ApplicationEvents;
import se.citerus.dddsample.application.BookingService;
import se.citerus.dddsample.domain.model.cargo.CargoRepository;
import se.citerus.dddsample.domain.model.handling.HandlingEventRepository;
import se.citerus.dddsample.domain.model.location.LocationRepository;
import se.citerus.dddsample.domain.model.voyage.VoyageRepository;
import se.citerus.dddsample.infrastructure.i18n.QueryParamLocaleResolver;
import se.citerus.dddsample.interfaces.booking.facade.BookingServiceFacade;
import se.citerus.dddsample.interfaces.booking.facade.internal.BookingServiceFacadeImpl;
import se.citerus.dddsample.interfaces.booking.web.CargoAdminController;
Expand All @@ -31,6 +33,7 @@

import java.io.File;
import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.Locale;

@Configuration
Expand All @@ -50,14 +53,25 @@ public class InterfacesApplicationContext implements WebMvcConfigurer {
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}

@Bean
public FixedLocaleResolver localeResolver() {
FixedLocaleResolver fixedLocaleResolver = new FixedLocaleResolver();
fixedLocaleResolver.setDefaultLocale(Locale.ENGLISH);
return fixedLocaleResolver;
public LocaleResolver localeResolver() {
QueryParamLocaleResolver localeResolver = new QueryParamLocaleResolver();
localeResolver.setSupportedLocales(List.of(Locale.ENGLISH,
Locale.SIMPLIFIED_CHINESE,
new Locale("sv", "SE"))); // add new locales here when available
localeResolver.setDefaultLocale(Locale.ENGLISH);
return localeResolver;
}

@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
lci.setParamName("lang");
return lci;
}

@Bean
Expand Down Expand Up @@ -87,6 +101,7 @@ public void addInterceptors(InterceptorRegistry registry) {
OpenEntityManagerInViewInterceptor openSessionInViewInterceptor = new OpenEntityManagerInViewInterceptor();
openSessionInViewInterceptor.setEntityManagerFactory(entityManager.getEntityManagerFactory());
registry.addWebRequestInterceptor(openSessionInViewInterceptor);
registry.addInterceptor(localeChangeInterceptor());
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.support.RequestContextUtils;
import se.citerus.dddsample.interfaces.booking.facade.BookingServiceFacade;
import se.citerus.dddsample.interfaces.booking.facade.dto.CargoRoutingDTO;
import se.citerus.dddsample.interfaces.booking.facade.dto.LegDTO;
Expand Down Expand Up @@ -40,9 +42,11 @@
public final class CargoAdminController {

private final BookingServiceFacade bookingServiceFacade;
private final MessageSource messageSource;

public CargoAdminController(BookingServiceFacade bookingServiceFacade) {
public CargoAdminController(BookingServiceFacade bookingServiceFacade, MessageSource messageSource) {
this.bookingServiceFacade = bookingServiceFacade;
this.messageSource = messageSource;
}

@InitBinder
Expand All @@ -54,7 +58,7 @@ private void initBinder(HttpServletRequest request, ServletRequestDataBinder bin
public String registration(HttpServletRequest request, HttpServletResponse response, Map<String, Object> model) throws Exception {
List<LocationDTO> dtoList = bookingServiceFacade.listShippingLocations();

List<String> unLocodeStrings = new ArrayList<String>();
List<String> unLocodeStrings = new ArrayList<>();

for (LocationDTO dto : dtoList) {
unLocodeStrings.add(dto.getUnLocode());
Expand Down Expand Up @@ -94,6 +98,7 @@ public String show(HttpServletRequest request, HttpServletResponse response, Map

@RequestMapping("/selectItinerary")
public String selectItinerary(HttpServletRequest request, HttpServletResponse response, Map<String, Object> model) throws Exception {
model.put("viewAdapter", new CargoAdminViewAdapter(messageSource, RequestContextUtils.getLocale(request)));
String trackingId = request.getParameter("trackingId");

List<RouteCandidateDTO> routeCandidates = bookingServiceFacade.requestPossibleRoutesForCargo(trackingId);
Expand All @@ -107,7 +112,7 @@ public String selectItinerary(HttpServletRequest request, HttpServletResponse re

@RequestMapping(value = "/assignItinerary", method = RequestMethod.POST)
public void assignItinerary(HttpServletRequest request, HttpServletResponse response, RouteAssignmentCommand command) throws Exception {
List<LegDTO> legDTOs = new ArrayList<LegDTO>(command.getLegs().size());
List<LegDTO> legDTOs = new ArrayList<>(command.getLegs().size());
for (RouteAssignmentCommand.LegCommand leg : command.getLegs()) {
legDTOs.add(new LegDTO(
leg.getVoyageNumber(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package se.citerus.dddsample.interfaces.booking.web;

import org.springframework.context.MessageSource;
import se.citerus.dddsample.interfaces.booking.facade.dto.CargoRoutingDTO;

import java.util.Locale;

public class CargoAdminViewAdapter {
private final MessageSource messageSource;
private final Locale locale;

public CargoAdminViewAdapter(MessageSource messageSource, Locale locale) {
this.messageSource = messageSource;
this.locale = locale;
}

public String getSelectItinerarySummaryText(CargoRoutingDTO cargo) {
return messageSource.getMessage("cargo.admin.itinerary.summary", new Object[]{cargo.getTrackingId(), cargo.getOrigin(), cargo.getFinalDestination()}, locale);
}

public String getRouteCandidateCaption(Integer index) {
return messageSource.getMessage("cargo.admin.itinerary.routecandidatecaption", new Object[]{index}, locale);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
* helps us shield the domain model classes.
* <p>
*
* @see CargoTrackingViewAdapter
* @see se.citerus.dddsample.interfaces.tracking.CargoTrackingViewAdapter
* @see se.citerus.dddsample.interfaces.booking.web.CargoAdminController
*/
@Controller
Expand Down Expand Up @@ -63,17 +63,21 @@ private String onSubmit(final HttpServletRequest request,
final TrackCommand command,
final Map<String, Object> model,
final BindingResult bindingResult) {
final Locale locale = RequestContextUtils.getLocale(request);
trackCommandValidator.validate(command, bindingResult);
if (bindingResult.hasErrors()) {
bindingResult.rejectValue("trackingId", "error.required");
return "track";
}

final TrackingId trackingId = new TrackingId(command.getTrackingId());
final Cargo cargo = cargoRepository.find(trackingId);

if (cargo != null) {
final Locale locale = RequestContextUtils.getLocale(request);
final List<HandlingEvent> handlingEvents = handlingEventRepository.lookupHandlingHistoryOfCargo(trackingId).distinctEventsByCompletionTime();
model.put("cargo", new CargoTrackingViewAdapter(cargo, messageSource, locale, handlingEvents));
} else {
bindingResult.rejectValue("trackingId", "cargo.unknown_id", new Object[]{command.getTrackingId()}, "Unknown tracking id");
bindingResult.rejectValue("trackingId", "cargo.unknown_id", new Object[]{command.getTrackingId()}, "");
}
return "track";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public final class CargoTrackingViewAdapter {
private final String FORMAT = "yyyy-MM-dd hh:mm";
private final TimeZone timeZone;

/**
/**
* Constructor.
*
* @param cargo
Expand Down Expand Up @@ -97,6 +97,14 @@ public String getStatusText() {
return messageSource.getMessage(code, args, "[Unknown status]", locale);
}

public String getEtaText() {
return messageSource.getMessage("cargo.routing.eta", new Object[]{getDestination(), getEta()}, locale);
}

public String getStatusDescriptionText() {
return messageSource.getMessage("cargo.routing.result", new Object[]{getTrackingId(), getStatusText()}, locale);
}

/**
* @return Cargo destination location.
*/
Expand Down Expand Up @@ -131,19 +139,20 @@ public String getNextExpectedActivity() {
return "";
}

String text = "Next expected activity is to ";
HandlingEvent.Type type = activity.type();
if (type.sameValueAs(HandlingEvent.Type.LOAD)) {
return
text + type.name().toLowerCase() + " cargo onto voyage " + activity.voyage().voyageNumber() +
" in " + activity.location().name();
} else if (type.sameValueAs(HandlingEvent.Type.UNLOAD)) {
return
text + type.name().toLowerCase() + " cargo off of " + activity.voyage().voyageNumber() +
" in " + activity.location().name();
} else {
return text + type.name().toLowerCase() + " cargo in " + activity.location().name();
}
return messageSource.getMessage("cargo.routing.nextexpact." + type.name(), new Object[]{activity.voyage().voyageNumber(), activity.location().name()}, "Missing translation string", locale);
} else if (type.sameValueAs(HandlingEvent.Type.UNLOAD)) {
return messageSource.getMessage("cargo.routing.nextexpact." + type.name(), new Object[]{activity.voyage().voyageNumber(), activity.location().name()}, "Missing translation string", locale);
} else if (type.sameValueAs(HandlingEvent.Type.RECEIVE)) {
return messageSource.getMessage("cargo.routing.nextexpact." + type.name(), new Object[]{activity.location().name()}, "Missing translation string", locale);
} else if (type.sameValueAs(HandlingEvent.Type.CLAIM)) {
return messageSource.getMessage("cargo.routing.nextexpact." + type.name(), new Object[]{activity.location().name()}, "Missing translation string", locale);
} else if (type.sameValueAs(HandlingEvent.Type.CUSTOMS)) {
return messageSource.getMessage("cargo.routing.nextexpact." + type.name(), new Object[]{activity.location().name()}, "Missing translation string", locale);
} else {
return messageSource.getMessage("cargo.routing.nextexpact.unknown", null, "Missing translation string", locale);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,5 @@ public boolean supports(@NonNull final Class<?> clazz) {
public void validate(@NonNull final Object object,@NonNull final Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "trackingId", "error.required", "Required");
}

}

Loading
Loading