diff --git a/sitebricks/src/main/java/com/google/sitebricks/Localizer.java b/sitebricks/src/main/java/com/google/sitebricks/Localizer.java index d74ea145..759b304a 100644 --- a/sitebricks/src/main/java/com/google/sitebricks/Localizer.java +++ b/sitebricks/src/main/java/com/google/sitebricks/Localizer.java @@ -1,5 +1,6 @@ package com.google.sitebricks; +import com.google.sitebricks.locale.LocaleProvider; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; @@ -40,7 +41,7 @@ public class Localizer { /** * A value object that represents the localization of an i18n interface to a locale - * and corresponding set of messages. + * and corresponding set of messages. */ public static class Localization { // TODO(dhanji): Convert class reference to weak? @@ -53,7 +54,7 @@ public Localization(Class clazz, Locale locale, Map messageBu this.locale = locale; this.messageBundle = messageBundle; } - + public Class getClazz() { return this.clazz; } @@ -168,7 +169,7 @@ private void bindMessageProvider(final Class iface, // Wonderful Guice hack to get around not using assisted inject. @Inject - private final Provider requestProvider = null; + final LocaleProvider localeProvider = null; // This is our delegate field that proxies the interface. private final Object instance = Proxy.newProxyInstance( @@ -179,7 +180,7 @@ private void bindMessageProvider(final Class iface, * Returns the localized message bundle value, keyed by the method name invoked. */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - Locale locale = requestProvider.get().getLocale(); + Locale locale = localeProvider.getLocale(); Map messages = getMessagesWithFallback(locale); // Use default if we don't support the given locale. @@ -189,7 +190,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl MessageDescriptor descriptor = messages.get(method.getName()); if (descriptor == null) { - throw new IllegalStateException("Could not find message '" + throw new IllegalStateException("Could not find message '" + method.getName() + "' in " + messages); } return descriptor.render(args); @@ -215,7 +216,7 @@ public Object get() { } - private String createLocaleInterfaceKey(final Class iface, Locale locale) { + private String createLocaleInterfaceKey(final Class iface, Locale locale) { return locale.toString() + ":" + iface.getName(); } diff --git a/sitebricks/src/main/java/com/google/sitebricks/SitebricksModule.java b/sitebricks/src/main/java/com/google/sitebricks/SitebricksModule.java index af6050d0..6877913d 100644 --- a/sitebricks/src/main/java/com/google/sitebricks/SitebricksModule.java +++ b/sitebricks/src/main/java/com/google/sitebricks/SitebricksModule.java @@ -1,5 +1,6 @@ package com.google.sitebricks; +import com.google.sitebricks.locale.LocaleProviderModule; import java.lang.annotation.Annotation; import java.util.Enumeration; import java.util.List; @@ -120,11 +121,20 @@ protected final void configure() { configureTemplateSystem(); + /* Now bind the locale provider.*/ + bindLocaleProvider(); + } + + /** + * Used to bind the Locale provider. Can be overwritten if custom Locale behavior is desider. + */ + protected void bindLocaleProvider() { + install(new LocaleProviderModule()); } protected void configureTemplateSystem() { // - // Map of all the implementations keyed by type they can handle + // Map of all the implementations keyed by type they can handle // ImmutableMap.Builder> builder = ImmutableMap.builder(); @@ -232,7 +242,7 @@ public void using(Locale locale, ResourceBundle bundle) { public void usingDefault() { add(Localizer.defaultLocalizationFor(iface)); } - + }; } @@ -246,7 +256,7 @@ private void add(Localizer.Localization localization) { } localeLocalizer.put(localization.getLocale(), localization); } - + protected final void scan(Package pack) { Preconditions.checkArgument(null != pack, "Package parameter to scan() cannot be null"); packages.add(pack); diff --git a/sitebricks/src/main/java/com/google/sitebricks/locale/LocaleProvider.java b/sitebricks/src/main/java/com/google/sitebricks/locale/LocaleProvider.java new file mode 100644 index 00000000..0ef1ecc7 --- /dev/null +++ b/sitebricks/src/main/java/com/google/sitebricks/locale/LocaleProvider.java @@ -0,0 +1,17 @@ +package com.google.sitebricks.locale; + +import java.util.Locale; + +/** + * Provides the {@link java.util.Locale} for the internationalization. + */ +public interface LocaleProvider { + + /** + * Retrieves the locale that is to be used for the i18n of the translatable messages. + * + * @return the requested {@link java.util.Locale}. + */ + Locale getLocale(); + +} diff --git a/sitebricks/src/main/java/com/google/sitebricks/locale/LocaleProviderImpl.java b/sitebricks/src/main/java/com/google/sitebricks/locale/LocaleProviderImpl.java new file mode 100644 index 00000000..e36b46fc --- /dev/null +++ b/sitebricks/src/main/java/com/google/sitebricks/locale/LocaleProviderImpl.java @@ -0,0 +1,38 @@ +package com.google.sitebricks.locale; + +import java.util.Locale; + +import javax.servlet.http.HttpServletRequest; + +import com.google.inject.Inject; +import com.google.inject.Provider; + +/** + * Provides a default implementation of the {@link LocaleProvider}. It retrieves the Locale as stored in the + * {@link javax.servlet.http.HttpServletRequest}. + */ +class LocaleProviderImpl implements LocaleProvider { + + private final Provider requestProvider; + + @Inject + LocaleProviderImpl(final Provider requestProvider) { + this.requestProvider = requestProvider; + } + + /** + * @return the Locale as stored in the {@link javax.servlet.http.HttpServletRequest}. + */ + public Locale getLocale() { + if (requestProvider == null) { + throw new IllegalStateException("The HttpServletRequest provider must be bound."); + } + + final HttpServletRequest request = requestProvider.get(); + if (request == null) { + throw new IllegalStateException("No HttpServletRequest could be retrieved. Cannot determine user locale."); + } + + return request.getLocale(); + } +} diff --git a/sitebricks/src/main/java/com/google/sitebricks/locale/LocaleProviderModule.java b/sitebricks/src/main/java/com/google/sitebricks/locale/LocaleProviderModule.java new file mode 100644 index 00000000..e5998a5f --- /dev/null +++ b/sitebricks/src/main/java/com/google/sitebricks/locale/LocaleProviderModule.java @@ -0,0 +1,11 @@ +package com.google.sitebricks.locale; + +import com.google.inject.AbstractModule; +import com.google.inject.Singleton; + +public class LocaleProviderModule extends AbstractModule { + @Override + protected final void configure() { + bind(LocaleProvider.class).to(LocaleProviderImpl.class).in(Singleton.class); + } +} diff --git a/sitebricks/src/test/java/com/google/sitebricks/LocalizationTest.java b/sitebricks/src/test/java/com/google/sitebricks/LocalizationTest.java index 5bfbb2ba..e7869ba2 100644 --- a/sitebricks/src/test/java/com/google/sitebricks/LocalizationTest.java +++ b/sitebricks/src/test/java/com/google/sitebricks/LocalizationTest.java @@ -9,6 +9,8 @@ import com.google.inject.Provider; import com.google.inject.name.Named; import com.google.sitebricks.i18n.Message; +import com.google.sitebricks.locale.LocaleProvider; +import org.easymock.EasyMock; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -29,15 +31,15 @@ */ public class LocalizationTest { private static final String HELLO = "hello"; - private HttpServletRequest requestMock; + private LocaleProvider localeProviderMock; @BeforeMethod public final void setup() { - requestMock = createNiceMock(HttpServletRequest.class); + localeProviderMock = createNiceMock(LocaleProvider.class); - expect(requestMock.getLocale()).andReturn(Locale.ENGLISH); + expect(localeProviderMock.getLocale()).andReturn(Locale.ENGLISH); - replay(requestMock); + replay(localeProviderMock); } @Test @@ -54,7 +56,7 @@ protected void configure() { Localizer.localizeAll(binder(), locs); - bind(HttpServletRequest.class).toInstance(requestMock); + bind(LocaleProvider.class).toInstance(localeProviderMock); } }).getInstance(Localized.class) .hello(); @@ -74,7 +76,7 @@ protected void configure() { locs.add(new Localizer.Localization(Localized.class, Locale.ENGLISH, resourceBundle)); Localizer.localizeAll(binder(), locs); - bind(HttpServletRequest.class).toInstance(requestMock); + bind(LocaleProvider.class).toInstance(localeProviderMock); } }); } @@ -92,7 +94,7 @@ protected void configure() { locs.add(new Localizer.Localization(LocalizedMissingAnnotation.class, Locale.ENGLISH, resourceBundle)); Localizer.localizeAll(binder(), locs); - bind(HttpServletRequest.class).toInstance(requestMock); + bind(LocaleProvider.class).toInstance(localeProviderMock); } }).getInstance(LocalizedMissingAnnotation.class); } @@ -111,7 +113,7 @@ protected void configure() { locs.add(new Localizer.Localization(LocalizedWrongReturnType.class, Locale.ENGLISH, resourceBundle)); Localizer.localizeAll(binder(), locs); - bind(HttpServletRequest.class).toInstance(requestMock); + bind(LocaleProvider.class).toInstance(localeProviderMock); } }).getInstance(LocalizedWrongReturnType.class); } @@ -130,7 +132,7 @@ protected void configure() { locs.add(new Localizer.Localization(LocalizedWrongArgAnnotation.class, Locale.ENGLISH, resourceBundle)); Localizer.localizeAll(binder(), locs); - bind(HttpServletRequest.class).toInstance(requestMock); + bind(LocaleProvider.class).toInstance(localeProviderMock); } }).getInstance(LocalizedWrongArgAnnotation.class); } @@ -150,7 +152,7 @@ protected void configure() { locs.add(new Localizer.Localization(LocalizedBrokenTemplate.class, Locale.ENGLISH, resourceBundle)); Localizer.localizeAll(binder(), locs); - bind(HttpServletRequest.class).toInstance(requestMock); + bind(LocaleProvider.class).toInstance(localeProviderMock); } }).getInstance(LocalizedBrokenTemplate.class); } @@ -169,7 +171,7 @@ protected void configure() { locs.add(new Localizer.Localization(LocalizedTemplate.class, Locale.ENGLISH, resourceBundle)); Localizer.localizeAll(binder(), locs); - bind(HttpServletRequest.class).toInstance(requestMock); + bind(LocaleProvider.class).toInstance(localeProviderMock); } }).getInstance(LocalizedTemplate.class) .hello("Dude"); @@ -186,15 +188,11 @@ public final void parameterizedLocalizeTemplateMultipleLocales() { resourceBundle.put(LocalizationTest.HELLO, "hello ${name}"); final HashMap japaneseBundle = Maps.newHashMap(); - japaneseBundle.put(LocalizationTest.HELLO, "konichiwa ${name}"); + japaneseBundle.put(LocalizationTest.HELLO, "konichiwa ${name} sama"); - // Simulate an Accept-Language of Japanese - HttpServletRequest japaneseRequest = createNiceMock(HttpServletRequest.class); - expect(japaneseRequest.getLocale()).andReturn(Locale.JAPANESE); - replay(japaneseRequest); - - final AtomicReference mockToUse - = new AtomicReference(japaneseRequest); + final LocaleProvider customLocaleProviderMock = createNiceMock(LocaleProvider.class); + expect(customLocaleProviderMock.getLocale()).andReturn(Locale.JAPANESE); + replay(customLocaleProviderMock); Injector injector = Guice.createInjector(new AbstractModule() { @Override @@ -204,38 +202,31 @@ protected void configure() { locs.add(new Localizer.Localization(LocalizedTemplate.class, Locale.JAPANESE, japaneseBundle)); Localizer.localizeAll(binder(), locs); - bind(HttpServletRequest.class).toProvider(new Provider() { - public HttpServletRequest get() { - return mockToUse.get(); - } - }); + bind(LocaleProvider.class).toInstance(customLocaleProviderMock); } }); String msg = injector.getInstance(LocalizedTemplate.class).hello("Dude"); - assert "konichiwa Dude".equals(msg) : msg; - - verify(japaneseRequest); + assert "konichiwa Dude sama".equals(msg) : msg; // Now let's simulate english. - mockToUse.set(requestMock); + EasyMock.reset(customLocaleProviderMock); + expect(customLocaleProviderMock.getLocale()).andReturn(Locale.ENGLISH); + replay(customLocaleProviderMock); + msg = injector.getInstance(LocalizedTemplate.class).hello("Dude"); assert "hello Dude".equals(msg); // Now let's simulate a totally different locale (should default to english). // Simulate an Accept-Language of French - HttpServletRequest frenchRequest = createNiceMock(HttpServletRequest.class); - expect(frenchRequest.getLocale()).andReturn(Locale.FRENCH); - replay(frenchRequest); - - mockToUse.set(frenchRequest); + EasyMock.reset(customLocaleProviderMock); + expect(customLocaleProviderMock.getLocale()).andReturn(Locale.FRENCH); + replay(customLocaleProviderMock); // Assert that it uses the english locale (set as default above) msg = injector.getInstance(LocalizedTemplate.class).hello("Dude"); assert "hello Dude".equals(msg); - - verify(frenchRequest, requestMock); } @@ -249,7 +240,7 @@ protected void configure() { Localizer.localizeAll(binder(), locs); - bind(HttpServletRequest.class).toInstance(requestMock); + bind(LocaleProvider.class).toInstance(localeProviderMock); } }).getInstance(LocalizedTemplate.class) .hello("Dudette");