Skip to content

Commit

Permalink
[SHIBUI-635]
Browse files Browse the repository at this point in the history
Initial support for i18n. Controller, configuration, tests, properties.
  • Loading branch information
Bill Smith committed Jul 18, 2018
1 parent 354747b commit 70c39cd
Show file tree
Hide file tree
Showing 11 changed files with 227 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -78,6 +82,11 @@ public AttributeUtility attributeUtility() {
@Autowired
Directory directory;

@Autowired
LocaleResolver localeResolver;

@Autowired
ResourceBundleMessageSource messageSource;

@Bean
public EntityDescriptorFilesScheduledTasks entityDescriptorFilesScheduledTasks(EntityDescriptorRepository entityDescriptorRepository) {
Expand All @@ -103,6 +112,13 @@ public EntityIdsSearchService entityIdsSearchService() {
};
}

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

/**
* A WebMvcConfigurer that won't mangle the path for the entities endpoint.
*
Expand Down Expand Up @@ -139,6 +155,11 @@ public String getOriginatingServletPath(HttpServletRequest request) {
helper.setUrlDecode(false);
configurer.setUrlPathHelper(helper);
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package edu.internet2.tier.shibboleth.admin.ui.configuration;

import edu.internet2.tier.shibboleth.admin.ui.i18n.MappedResourceBundleMessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

@Configuration
public class InternationalizationConfiguration {
@Bean
public LocaleResolver localeResolver() {
// TODO if we want to control the order, we can implement our own locale resolver instead of using the SessionLocaleResolver.
SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
return sessionLocaleResolver;
}

@Bean
public MappedResourceBundleMessageSource messageSource() {
MappedResourceBundleMessageSource source = new MappedResourceBundleMessageSource();
source.setBasenames("i18n/messages");
source.setUseCodeAsDefaultMessage(true);
return source;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package edu.internet2.tier.shibboleth.admin.ui.controller;

import edu.internet2.tier.shibboleth.admin.ui.i18n.MappedResourceBundleMessageSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Locale;

/**
* @author Bill Smith (wsmith@unicon.net)
*/
@Controller
@RequestMapping(value = "/api/messages")
public class InternationalizationMessagesController {
@Autowired
MappedResourceBundleMessageSource messageSource;

@GetMapping
public ResponseEntity<?> getAll(Locale locale) {
return ResponseEntity.ok(messageSource.getMessagesMap(locale));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package edu.internet2.tier.shibboleth.admin.ui.i18n;

import org.springframework.context.support.ResourceBundleMessageSource;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;

/**
* @author Bill Smith (wsmith@unicon.net)
*/
public class MappedResourceBundleMessageSource extends ResourceBundleMessageSource {
public Map<String, String> getMessagesMap(Locale locale) {
ResourceBundle resourceBundle = this.doGetBundle("i18n/messages", locale);
Map<String, String> messagesMap = new HashMap<>();
Enumeration bundleKeys = resourceBundle.getKeys();

while (bundleKeys.hasMoreElements()) {
String key = (String)bundleKeys.nextElement();
String value = resourceBundle.getString(key);
messagesMap.put(key, value);
}

return messagesMap;
}
}
10 changes: 10 additions & 0 deletions backend/src/main/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Fill this file with key/value pairs, as follows:
#
# some.test.message=This is a test message.
#
# Then, create a copy using the name of the language code:
#
# messages_<code>.properties
#
# Do this for each language we want to support.
# Ideally, all messages should exist for each language.
1 change: 1 addition & 0 deletions backend/src/main/resources/i18n/messages_en.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a.sample.message=This is a sample message.
1 change: 1 addition & 0 deletions backend/src/main/resources/i18n/messages_fr.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a.sample.message=Le francais est tres difficile.
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package edu.internet2.tier.shibboleth.admin.ui.controller

import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration
import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration
import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration
import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration
import edu.internet2.tier.shibboleth.admin.ui.i18n.MappedResourceBundleMessageSource
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.domain.EntityScan
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import org.springframework.web.servlet.LocaleResolver
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor
import spock.lang.Specification

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content

/**
* @author Bill Smith (wsmith@unicon.net)
*/
@DataJpaTest
@ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration, TestConfiguration, InternationalizationConfiguration])
@EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"])
@EntityScan("edu.internet2.tier.shibboleth.admin.ui")
class InternationalizationMessagesControllerTests extends Specification {
@Autowired
MappedResourceBundleMessageSource messageSource

@Autowired
LocaleChangeInterceptor localeChangeInterceptor

@Autowired
LocaleResolver localResolver

def controller
def mockMvc

def setup() {
controller = new InternationalizationMessagesController(
messageSource: messageSource
)

mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setLocaleResolver(localResolver)
.addInterceptors(localeChangeInterceptor)
.build()
}

def messagesUrl = "/api/messages"

def expectedEnglishResult =
'{' +
' "some.test.message": "This is the English test message."' +
'}'

def expectedFrenchResult =
'{' +
' "some.test.message": "Je ne sais pas Francais."' +
'}'

def "GET messages with no header or \"lang\" param defaults to returning english messages"() {
when:
def result = mockMvc.perform(
get(messagesUrl))

then:
result.andExpect(content().json(expectedEnglishResult))
}

def "GET messages with Accept-Language returns messages in that language"() {
when:
def result = mockMvc.perform(
get(messagesUrl)
.header("Accept-Language", "fr"))

then:
result.andExpect(content().json(expectedFrenchResult))
}

def "GET messages with \"lang\" request param returns messages in that language"() {
when:
def result = mockMvc.perform(
get(messagesUrl)
.param("lang", "fr"))

then:
result.andExpect(content().json(expectedFrenchResult))
}

def "GET messages with both Accept-Language header and \"lang\" request param returns messages in the language specified by the \"lang\" parameter"() {
when:
def result = mockMvc.perform(
get(messagesUrl)
.header("Accept-Language", "en")
.param("lang", "fr"))

then:
result.andExpect(content().json(expectedFrenchResult))
}

def "GET messages with an unsupported Accept-Language returns the default language"() {
when:
def result = mockMvc.perform(
get(messagesUrl)
.header("Accept-Language", "es"))

then:
result.andExpect(content().json(expectedEnglishResult))
}
}
1 change: 1 addition & 0 deletions backend/src/test/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
some.test.message=This is the default message.
1 change: 1 addition & 0 deletions backend/src/test/resources/i18n/messages_en.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
some.test.message=This is the English test message.
1 change: 1 addition & 0 deletions backend/src/test/resources/i18n/messages_fr.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
some.test.message=Je ne sais pas Francais.

0 comments on commit 70c39cd

Please sign in to comment.