Reputation: 19497
I'm writing a custom Spring Boot starter that other developers will drop into their applications, and this starter contains out-of-the-box controllers and UI screens.
These UI screens are internationalized and the i18n keys/values are in a package file: com/foo/wherever/i18n.properties
.
I want to ensure that when my starter is loaded at startup, that these i18n.properties are available in the application's MessageSource
automatically so that my UI pages work (rendered via normal Spring Controller + ViewResolver + View implementations) without the app developer having to specify this file themselves.
In other words, they should be able to add my starter to their runtime classpath and everything 'just works' without the need to configure anything.
Now, I have discovered that the app developer can create their own src/main/resources/messages.properties
file and manually configure the additional messages file in application.properties
:
spring.messages.basename = messages, com.foo.wherever.i18n
And this will work.
However, this requires both of the following:
spring.messages.basename
property - it's not automatic. and messages.properties
file in their application classpath. If a messages.properties
file does not exist, spring.messages.basename
doesn't even work. Even if they don't care about i18n, this is still required - not desirable.I suppose I could move my i18n.properties file to a classpath:/messages.properties file in the starter .jar, but that doesn't seem like a good solution: if the app dev has their own messages.properties file only one of them would be read, resulting in missing message values.
It seems as if Spring Boot MessageSourceAutoConfiguration should have a concept of a CompositeMessageSource
that iterates over one or more MessageSource
instances that are available (and Order
ed) in the Spring ApplicationContext and that is used by the DispatcherServlet. This would allow any starter to contribute to the available messages just by declaring a MessageSource
in their auto config
Is it possible to do what I ask? What is the most 'hands off' solution for the app developer?
Upvotes: 11
Views: 26396
Reputation: 5449
I realize this is an old and answered question by now, but I encountered the same problem the other day, and wrote a blog post about how I've decided to solve it. I thought I should share it here since I got some inspiration for my solution from this thread.
In short, it takes sodik's idea of intercepting the creation of the MessageSource
bean, but instead of using a BeanFactoryPostProcessor
I'm using a BeanPostProcessor
, and rather than replacing the original MessageSource
in the application context, I just add my own as its parent:
@Bean
BeanPostProcessor messageSourceCustomExtender() {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof HierarchicalMessageSource && beanName.equals("messageSource")) {
ResourceBundleMessageSource parent = new ResourceBundleMessageSource();
parent.setBasename("custom");
((HierarchicalMessageSource) bean).setParentMessageSource(parent);
}
return bean;
}
};
}
You can read the full blog post where I explain some caveats about my solution: http://www.thomaskasene.com/2016/08/20/custom-spring-boot-starter-messagesource/
After some tinkering I realized that using a BeanFactoryPostProcessor
was wrong as it will cause the original MessageSource
bean to be created prematurely and ignore application properties (most importantly, spring.messages.basename
). That means the application won't be able to configure these properties. See the excerpt from the BeanFactoryPostProcessor documentation below.
A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances. Doing so may cause premature bean instantiation, violating the container and causing unintended side-effects. If bean instance interaction is required, consider implementing BeanPostProcessor instead.
I've updated the example above to use BeanPostProcessor
instead, which alters the bean instance rather than the bean definition.
Upvotes: 2
Reputation: 10737
I set this up in the following way. I'm only currently supporting en_US but it is setup to handle any number of languages using internationalization(i18n).
View the code gist here: Code on github gist
Add these beans to your Application.java to set your default locale and configure the location of your message props
Service will get the default locale from the session and then get the message text from your props
Inject the message svc and then pass in the id to get the value from the props file
Goto /resources:
You can view a more complete article on this subject here: Spring Boot Internationalization i18n using Message Properties
View the code gist here: Code on github gist
Upvotes: 17
Reputation: 4683
Maybe it is long shot but you can try to use BeanFactoryPostProcessor.
Idea is following:
Take "messageSource" bean out of the application context. Note that it might but does not have to be a spring boot's one if e.g. developer wants to use its own implementation and don't use spring boot autoconfiguration.
Replace it with your own implementation that tries to resolve "your keys" and the rest delegate to original message source. Or vice versa if you want to make possible to override your translations by developer (there can be problems if original message source does not throw exception for unknown keys).
But there might be a better way to do that.
Upvotes: 2