Dmytro Nosan opened SPR-16614 and commented
Sometimes to localize some bean we have to use several sources. e.g.
MessageSource m1 = db.getTranslations()
MessageSource m2 = someService.getTranslations();
and then we will use a custom method for localizing
private String translate(MessageSource m1, MessageSource m2, String code, Locale locale) {
try {
return m1.getMessage(code, null, locale);
} catch (NoSuchMessageException ex) {
return m2.getMessage(code, null, locale);
}
}
But this method has several disadvantages: 1. If m1 has (useCodeAsDefaultMessage) we will never get a message from m2. 2. A lot of exceptions could be thrown 3. What would we do, if we need one or more MessageSource(s)?
My idea is to create a new ComposedMessageSource class which should handle: 1. Resolve message from both MessageSources 2. Resolve situation when one of the MessageSources has a property useCodeAsDefaultMessage (we should find the best option for localization) 3. Unnecessary exception generation
also, it would be great to have a utility class for composing MessageSources.
public abstract class MessageSources {
@Nullable
public static MessageSource compose(@Nullable MessageSource messageSource, @Nullable MessageSource parentMessageSource){
...
}
Affects: 5.0.4
Attachments: - ComposedMessageSource.java (4.30 kB) - ComposedMessageSourceTests.java (5.75 kB) - MessageSource.java (3.86 kB) - MessageSources.java (1.28 kB) - MessageSourcesTest.java (686 bytes)
Comment From: spring-projects-issues
Juergen Hoeller commented
We have a 'parent' arrangement for MessageSource
providers which provides delegation to the parent for a message not found in the given source. Could you leverage that for your purposes, building a chain of MessageSource
delegates?
Comment From: spring-projects-issues
Dmytro Nosan commented
Thanks for the answer.
In general, you can build a chain with:
public class MessageSourceComposite implements MessageSource {
private final List<MessageSource> messageSources = new ArrayList<>();
public void add(MessageSource... messageSources) {
if (messageSources != null) {
for (MessageSource messageSource : messageSources) {
if (messageSource != null) {
this.messageSources.add(messageSource);
}
}
}
}
@Override
public String getMessage(String code, Object[] args, String defaultMessage, Locale locale) {
try {
return getMessage(code, args, locale);
} catch (NoSuchMessageException ex) {
return defaultMessage;
}
}
@Override
public String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException {
for (MessageSource messageSource : messageSources) {
try {
return messageSource.getMessage(code, args, locale);
} catch (NoSuchMessageException ignored) {
}
}
throw new NoSuchMessageException(code, locale);
}
@Override
public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
for (MessageSource messageSource : messageSources) {
try {
return messageSource.getMessage(resolvable, locale);
} catch (NoSuchMessageException ignored) {
}
}
String[] codes = resolvable.getCodes();
throw new NoSuchMessageException(!ObjectUtils.isEmpty(codes) ? codes[codes.length - 1] : null, locale);
}
}
But you will still have an issue with useCodeAsDefaultMessage and exception generation. Could you please provide your own solution?
Thanks
Comment From: spring-projects-issues
Juergen Hoeller commented
I was referring to AbstractMessageSource.getMessageInternal/getMessageFromParent
and its use of a parent hierarchy coming from setParentMessageSource
(which can also be set via the HierarchicalMessageSource
interface). Through internal delegation to getMessageInternal
in case of an AbstractMessageSource
parent, we also bypass the useCodeAsDefaultMessage
problem and do not rely on an exception for the not-found case.
Since we have such a delegation model in the core framework already, we won't add another variant out of the box. If this is not sufficient for your purposes, you're free to build your own on top, of course.
Comment From: spring-projects-issues
Dmytro Nosan commented
Thanks for the explanation. It is really great, but it would be nice to have a support this feature from the box. I believe that it would help not only in this particular case, which I have described, it will simplify work with a conjunction of several message sources in general.
public class ComposedMessageSource extends AbstractMessageSource {
@Nullable
private MessageSource messageSource;
@Nullable
public MessageSource getMessageSource() {
return messageSource;
}
public void setMessageSource(@Nullable MessageSource messageSource) {
this.messageSource = messageSource;
}
protected String getMessageInternal(String code, Object[] args, Locale locale) {
String message;
if (messageSource != null) {
if (messageSource instanceof AbstractMessageSource) {
message = ((AbstractMessageSource) messageSource).getMessageInternal(code, args, locale);
} else {
//custom messageSource
message = messageSource.getMessage(code, args, null, locale);
}
if (message != null) {
return message;
}
}
message = getMessageFromParent(code, args, locale);
if (message != null) {
return message;
}
//should be applied after both messageSources.
Properties commonMessages = getCommonMessages();
if (commonMessages != null) {
String commonMessage = commonMessages.getProperty(code);
if (commonMessage != null) {
return formatMessage(commonMessage, args, locale);
}
}
return null;
}
@Override
protected MessageFormat resolveCode(String code, Locale locale) {
return null; //maybe UnsupportedOperation ?
}
}
Thanks.
Comment From: snicoll
I believe this is more natural to use a hierarchy rather than the composite. Besides there hasn't been a lot of traction for this request so I am going to close it. Thanks for the suggestion, in any case!