From d2fb59a992cb994dff896bb58718ac097da220e6 Mon Sep 17 00:00:00 2001 From: Matthias Stock Date: Sat, 11 Mar 2017 22:52:09 +0100 Subject: [PATCH] Locale Lookup Trying to find a best-match for a given locale. Otherwise, returning a fallback but never returning an unsupported locale. --- .../de/mstock/monolith/config/I18nConfig.java | 9 ++- .../AcceptHeaderLookupLocaleResolver.java | 44 ++++++++++ src/main/resources/messages.properties | 17 ---- src/main/resources/messages_de.properties | 2 +- src/main/resources/messages_en.properties | 17 ++++ .../monolith/MonolithApplicationTests.java | 19 ----- .../monolith/web/HomepageControllerTest.java | 80 +++++++++++++++++++ 7 files changed, 148 insertions(+), 40 deletions(-) create mode 100644 src/main/java/de/mstock/monolith/web/i18n/AcceptHeaderLookupLocaleResolver.java create mode 100644 src/main/resources/messages_en.properties delete mode 100644 src/test/java/de/mstock/monolith/MonolithApplicationTests.java create mode 100644 src/test/java/de/mstock/monolith/web/HomepageControllerTest.java diff --git a/src/main/java/de/mstock/monolith/config/I18nConfig.java b/src/main/java/de/mstock/monolith/config/I18nConfig.java index 4e63724..04d42e3 100644 --- a/src/main/java/de/mstock/monolith/config/I18nConfig.java +++ b/src/main/java/de/mstock/monolith/config/I18nConfig.java @@ -10,11 +10,13 @@ import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; +import de.mstock.monolith.web.i18n.AcceptHeaderLookupLocaleResolver; + @Configuration public class I18nConfig extends WebMvcConfigurerAdapter { private static final List SUPPORTED_LOCALES = - Arrays.asList(new Locale("de_DE"), new Locale("en_US")); + Arrays.asList(new Locale("en", "US"), new Locale("de", "DE")); /** * Creates a Bean, managed by Spring. @@ -23,9 +25,10 @@ public class I18nConfig extends WebMvcConfigurerAdapter { */ @Bean public LocaleResolver localeResolver() { - AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); + Locale defaultLocale = SUPPORTED_LOCALES.get(0); + AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLookupLocaleResolver(defaultLocale); localeResolver.setSupportedLocales(SUPPORTED_LOCALES); - localeResolver.setDefaultLocale(Locale.US); + localeResolver.setDefaultLocale(defaultLocale); return localeResolver; } diff --git a/src/main/java/de/mstock/monolith/web/i18n/AcceptHeaderLookupLocaleResolver.java b/src/main/java/de/mstock/monolith/web/i18n/AcceptHeaderLookupLocaleResolver.java new file mode 100644 index 0000000..581396e --- /dev/null +++ b/src/main/java/de/mstock/monolith/web/i18n/AcceptHeaderLookupLocaleResolver.java @@ -0,0 +1,44 @@ +package de.mstock.monolith.web.i18n; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Locale.LanguageRange; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; + +public class AcceptHeaderLookupLocaleResolver extends AcceptHeaderLocaleResolver { + + private final Locale fallback; + private final ArrayList ranges = new ArrayList<>(); + + public AcceptHeaderLookupLocaleResolver(Locale fallback) { + this.fallback = fallback; + } + + @Override + public void setSupportedLocales(List locales) { + super.setSupportedLocales(locales); + ranges.clear(); + for (Locale supportedLocale : getSupportedLocales()) { + ranges.add(new LanguageRange(supportedLocale.getLanguage() + "-*")); + } + } + + @Override + public Locale resolveLocale(HttpServletRequest request) { + Locale resolvedLocale = super.resolveLocale(request); + if (getSupportedLocales().contains(resolvedLocale)) { + return resolvedLocale; + } + Locale lookup = Locale.lookup(ranges, Arrays.asList(resolvedLocale)); + if (lookup != null) { + return lookup; + } + return fallback; + } + +} diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index dc69cb4..44caf11 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -1,18 +1 @@ company = Mosh -general.backToTop = Back to top -general.sampleImage = Sample image -navigation.toggle = Toggle navigation -navigation.legalNotice = Legal Notice -products.kiwis=Kiwis -products.blueberries=Blueberries -products.cherries=Cherries -products.kiwis.prettyUrlFragment=kiwis -products.blueberries.prettyUrlFragment=blueberries -products.cherries.prettyUrlFragment=cherries -products.goto = Go to product -features.headline1 = Delicious Fruits. -features.subheadline1 = It'll blow your mind. -features.headline2 = Oh yeah, it's that good. -features.subheadline2 = See for yourself. -features.headline3 = And lastly, this one. -features.subheadline3 = Checkmate. \ No newline at end of file diff --git a/src/main/resources/messages_de.properties b/src/main/resources/messages_de.properties index b19516f..1cbc288 100644 --- a/src/main/resources/messages_de.properties +++ b/src/main/resources/messages_de.properties @@ -14,4 +14,4 @@ features.subheadline1 = Es wird dich umhauen. features.headline2 = Das ist gut. features.subheadline2 = Sieh' selbst. features.headline3 = Und zum Schluss, das hier. -features.subheadline3 = Bingo. \ No newline at end of file +features.subheadline3 = Bingo. diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties new file mode 100644 index 0000000..c217fdd --- /dev/null +++ b/src/main/resources/messages_en.properties @@ -0,0 +1,17 @@ +general.backToTop = Back to top +general.sampleImage = Sample image +navigation.toggle = Toggle navigation +navigation.legalNotice = Legal Notice +products.kiwis=Kiwis +products.blueberries=Blueberries +products.cherries=Cherries +products.kiwis.prettyUrlFragment=kiwis +products.blueberries.prettyUrlFragment=blueberries +products.cherries.prettyUrlFragment=cherries +products.goto = Go to product +features.headline1 = Delicious Fruits. +features.subheadline1 = It'll blow your mind. +features.headline2 = Oh yeah, it's that good. +features.subheadline2 = See for yourself. +features.headline3 = And lastly, this one. +features.subheadline3 = Checkmate. diff --git a/src/test/java/de/mstock/monolith/MonolithApplicationTests.java b/src/test/java/de/mstock/monolith/MonolithApplicationTests.java deleted file mode 100644 index 57e04c8..0000000 --- a/src/test/java/de/mstock/monolith/MonolithApplicationTests.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.mstock.monolith; - -import static org.junit.Assert.assertTrue; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class MonolithApplicationTests { - - @Test - public void contextLoads() { - assertTrue(true); - } - -} diff --git a/src/test/java/de/mstock/monolith/web/HomepageControllerTest.java b/src/test/java/de/mstock/monolith/web/HomepageControllerTest.java new file mode 100644 index 0000000..40d52ae --- /dev/null +++ b/src/test/java/de/mstock/monolith/web/HomepageControllerTest.java @@ -0,0 +1,80 @@ +package de.mstock.monolith.web; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.Locale; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringRunner.class) +@SpringBootTest +@WebAppConfiguration +public class HomepageControllerTest { + + @Autowired + private WebApplicationContext webApplicationContext; + + private MockMvc mockMvc; + + @Before + public void setup() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + } + + @Test + public void shouldPrintEnglishTexts() throws Exception { + mockMvc.perform(get("/").header(HttpHeaders.ACCEPT_LANGUAGE, "en").locale(new Locale("en"))) + .andExpect(status().isOk()).andExpect(content().string(containsString("Fruits"))) + .andExpect(content().string(containsString("to top"))); + } + + @Test + public void shouldPrintGermanTexts() throws Exception { + mockMvc.perform(get("/").header(HttpHeaders.ACCEPT_LANGUAGE, "de").locale(new Locale("de"))) + .andExpect(status().isOk()).andExpect(status().isOk()) + .andExpect(content().string(containsString("Obst"))) + .andExpect(content().string(containsString("nach oben"))); + } + + @Test + public void shouldPrintEnglishTextsForAustralia() throws Exception { + mockMvc + .perform( + get("/").header(HttpHeaders.ACCEPT_LANGUAGE, "en-AU").locale(new Locale("en", "AU"))) + .andExpect(status().isOk()).andExpect(content().string(containsString("Fruits"))) + .andExpect(content().string(containsString("to top"))); + } + + @Test + public void shouldPrintGermanTextsForAustria() throws Exception { + mockMvc + .perform( + get("/").header(HttpHeaders.ACCEPT_LANGUAGE, "de-AT").locale(new Locale("de", "AT"))) + .andExpect(status().isOk()).andExpect(status().isOk()) + .andExpect(content().string(containsString("Obst"))) + .andExpect(content().string(containsString("nach oben"))); + } + + @Test + public void shouldPrintEnglishTextsForChina() throws Exception { + mockMvc + .perform( + get("/").header(HttpHeaders.ACCEPT_LANGUAGE, "zh-CN").locale(new Locale("zh", "CN"))) + .andExpect(status().isOk()).andExpect(content().string(containsString("Fruits"))) + .andExpect(content().string(containsString("to top"))); + } + +}