Browse Source

Merge pull request #42472 from mmoayyed

* pr/42472:
  Polish 'Allow common messages to be specified for message sources'
  Allow common messages to be specified for message sources

Closes gh-42472
pull/42725/head
Phillip Webb 1 year ago
parent
commit
4f96beadc3
  1. 23
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/MessageSourceAutoConfiguration.java
  2. 14
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/MessageSourceProperties.java
  3. 50
      spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/context/MessageSourceAutoConfigurationTests.java
  4. 1
      spring-boot-project/spring-boot-autoconfigure/src/test/resources/test/common-messages.properties
  5. 6
      spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/internationalization.adoc

23
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/MessageSourceAutoConfiguration.java

@ -16,7 +16,11 @@ @@ -16,7 +16,11 @@
package org.springframework.boot.autoconfigure.context;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.time.Duration;
import java.util.List;
import java.util.Properties;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
@ -38,9 +42,11 @@ import org.springframework.context.annotation.Conditional; @@ -38,9 +42,11 @@ import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.CollectionFactory;
import org.springframework.core.Ordered;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
@ -81,9 +87,26 @@ public class MessageSourceAutoConfiguration { @@ -81,9 +87,26 @@ public class MessageSourceAutoConfiguration {
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
messageSource.setCommonMessages(loadCommonMessages(properties.getCommonMessages()));
return messageSource;
}
private Properties loadCommonMessages(List<Resource> resources) {
if (CollectionUtils.isEmpty(resources)) {
return null;
}
Properties properties = CollectionFactory.createSortedProperties(false);
for (Resource resource : resources) {
try {
PropertiesLoaderUtils.fillProperties(properties, resource);
}
catch (IOException ex) {
throw new UncheckedIOException("Failed to load common messages from '%s'".formatted(resource), ex);
}
}
return properties;
}
protected static class ResourceBundleCondition extends SpringBootCondition {
private static final ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>();

14
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/MessageSourceProperties.java

@ -25,6 +25,7 @@ import java.util.List; @@ -25,6 +25,7 @@ import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.core.io.Resource;
/**
* Configuration properties for Message Source.
@ -44,6 +45,11 @@ public class MessageSourceProperties { @@ -44,6 +45,11 @@ public class MessageSourceProperties {
*/
private List<String> basename = new ArrayList<>(List.of("messages"));
/**
* List of locale-independent property file resources containing common messages.
*/
private List<Resource> commonMessages;
/**
* Message bundles encoding.
*/
@ -123,4 +129,12 @@ public class MessageSourceProperties { @@ -123,4 +129,12 @@ public class MessageSourceProperties {
this.useCodeAsDefaultMessage = useCodeAsDefaultMessage;
}
public List<Resource> getCommonMessages() {
return this.commonMessages;
}
public void setCommonMessages(List<Resource> commonMessages) {
this.commonMessages = commonMessages;
}
}

50
spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/context/MessageSourceAutoConfigurationTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -58,7 +58,7 @@ class MessageSourceAutoConfigurationTests { @@ -58,7 +58,7 @@ class MessageSourceAutoConfigurationTests {
@Test
void propertiesBundleWithSlashIsDetected() {
this.contextRunner.withPropertyValues("spring.messages.basename:test/messages").run((context) -> {
this.contextRunner.withPropertyValues("spring.messages.basename=test/messages").run((context) -> {
assertThat(context).hasSingleBean(MessageSource.class);
assertThat(context.getMessage("foo", null, "Foo message", Locale.UK)).isEqualTo("bar");
});
@ -66,7 +66,7 @@ class MessageSourceAutoConfigurationTests { @@ -66,7 +66,7 @@ class MessageSourceAutoConfigurationTests {
@Test
void propertiesBundleWithDotIsDetected() {
this.contextRunner.withPropertyValues("spring.messages.basename:test.messages").run((context) -> {
this.contextRunner.withPropertyValues("spring.messages.basename=test.messages").run((context) -> {
assertThat(context).hasSingleBean(MessageSource.class);
assertThat(context.getMessage("foo", null, "Foo message", Locale.UK)).isEqualTo("bar");
});
@ -74,7 +74,7 @@ class MessageSourceAutoConfigurationTests { @@ -74,7 +74,7 @@ class MessageSourceAutoConfigurationTests {
@Test
void testEncodingWorks() {
this.contextRunner.withPropertyValues("spring.messages.basename:test/swedish")
this.contextRunner.withPropertyValues("spring.messages.basename=test/swedish")
.run((context) -> assertThat(context.getMessage("foo", null, "Foo message", Locale.UK))
.isEqualTo("Some text with some swedish öäå!"));
}
@ -82,14 +82,14 @@ class MessageSourceAutoConfigurationTests { @@ -82,14 +82,14 @@ class MessageSourceAutoConfigurationTests {
@Test
void testCacheDurationNoUnit() {
this.contextRunner
.withPropertyValues("spring.messages.basename:test/messages", "spring.messages.cache-duration=10")
.withPropertyValues("spring.messages.basename=test/messages", "spring.messages.cache-duration=10")
.run(assertCache(10 * 1000));
}
@Test
void testCacheDurationWithUnit() {
this.contextRunner
.withPropertyValues("spring.messages.basename:test/messages", "spring.messages.cache-duration=1m")
.withPropertyValues("spring.messages.basename=test/messages", "spring.messages.cache-duration=1m")
.run(assertCache(60 * 1000));
}
@ -102,7 +102,7 @@ class MessageSourceAutoConfigurationTests { @@ -102,7 +102,7 @@ class MessageSourceAutoConfigurationTests {
@Test
void testMultipleMessageSourceCreated() {
this.contextRunner.withPropertyValues("spring.messages.basename:test/messages,test/messages2")
this.contextRunner.withPropertyValues("spring.messages.basename=test/messages,test/messages2")
.run((context) -> {
assertThat(context.getMessage("foo", null, "Foo message", Locale.UK)).isEqualTo("bar");
assertThat(context.getMessage("foo-foo", null, "Foo-Foo message", Locale.UK)).isEqualTo("bar-bar");
@ -116,9 +116,27 @@ class MessageSourceAutoConfigurationTests { @@ -116,9 +116,27 @@ class MessageSourceAutoConfigurationTests {
.run((context) -> assertThat(context.getMessage("foo", null, "Foo message", Locale.UK)).isEqualTo("bar"));
}
@Test
void testCommonMessages() {
this.contextRunner
.withPropertyValues("spring.messages.basename=test/messages",
"spring.messages.common-messages=classpath:test/common-messages.properties")
.run((context) -> assertThat(context.getMessage("hello", null, "Hello!", Locale.UK)).isEqualTo("world"));
}
@Test
void testCommonMessagesWhenNotFound() {
this.contextRunner
.withPropertyValues("spring.messages.basename=test/messages",
"spring.messages.common-messages=classpath:test/common-messages-missing.properties")
.run((context) -> assertThat(context).getFailure()
.hasMessageContaining(
"Failed to load common messages from 'class path resource [test/common-messages-missing.properties]'"));
}
@Test
void testFallbackDefault() {
this.contextRunner.withPropertyValues("spring.messages.basename:test/messages")
this.contextRunner.withPropertyValues("spring.messages.basename=test/messages")
.run((context) -> assertThat(context.getBean(MessageSource.class))
.hasFieldOrPropertyWithValue("fallbackToSystemLocale", true));
}
@ -126,7 +144,7 @@ class MessageSourceAutoConfigurationTests { @@ -126,7 +144,7 @@ class MessageSourceAutoConfigurationTests {
@Test
void testFallbackTurnOff() {
this.contextRunner
.withPropertyValues("spring.messages.basename:test/messages",
.withPropertyValues("spring.messages.basename=test/messages",
"spring.messages.fallback-to-system-locale:false")
.run((context) -> assertThat(context.getBean(MessageSource.class))
.hasFieldOrPropertyWithValue("fallbackToSystemLocale", false));
@ -134,7 +152,7 @@ class MessageSourceAutoConfigurationTests { @@ -134,7 +152,7 @@ class MessageSourceAutoConfigurationTests {
@Test
void testFormatMessageDefault() {
this.contextRunner.withPropertyValues("spring.messages.basename:test/messages")
this.contextRunner.withPropertyValues("spring.messages.basename=test/messages")
.run((context) -> assertThat(context.getBean(MessageSource.class))
.hasFieldOrPropertyWithValue("alwaysUseMessageFormat", false));
}
@ -142,7 +160,7 @@ class MessageSourceAutoConfigurationTests { @@ -142,7 +160,7 @@ class MessageSourceAutoConfigurationTests {
@Test
void testFormatMessageOn() {
this.contextRunner
.withPropertyValues("spring.messages.basename:test/messages",
.withPropertyValues("spring.messages.basename=test/messages",
"spring.messages.always-use-message-format:true")
.run((context) -> assertThat(context.getBean(MessageSource.class))
.hasFieldOrPropertyWithValue("alwaysUseMessageFormat", true));
@ -150,7 +168,7 @@ class MessageSourceAutoConfigurationTests { @@ -150,7 +168,7 @@ class MessageSourceAutoConfigurationTests {
@Test
void testUseCodeAsDefaultMessageDefault() {
this.contextRunner.withPropertyValues("spring.messages.basename:test/messages")
this.contextRunner.withPropertyValues("spring.messages.basename=test/messages")
.run((context) -> assertThat(context.getBean(MessageSource.class))
.hasFieldOrPropertyWithValue("useCodeAsDefaultMessage", false));
}
@ -158,8 +176,8 @@ class MessageSourceAutoConfigurationTests { @@ -158,8 +176,8 @@ class MessageSourceAutoConfigurationTests {
@Test
void testUseCodeAsDefaultMessageOn() {
this.contextRunner
.withPropertyValues("spring.messages.basename:test/messages",
"spring.messages.use-code-as-default-message:true")
.withPropertyValues("spring.messages.basename=test/messages",
"spring.messages.use-code-as-default-message=true")
.run((context) -> assertThat(context.getBean(MessageSource.class))
.hasFieldOrPropertyWithValue("useCodeAsDefaultMessage", true));
}
@ -173,13 +191,13 @@ class MessageSourceAutoConfigurationTests { @@ -173,13 +191,13 @@ class MessageSourceAutoConfigurationTests {
@Test
void existingMessageSourceInParentIsIgnored() {
this.contextRunner.run((parent) -> this.contextRunner.withParent(parent)
.withPropertyValues("spring.messages.basename:test/messages")
.withPropertyValues("spring.messages.basename=test/messages")
.run((context) -> assertThat(context.getMessage("foo", null, "Foo message", Locale.UK)).isEqualTo("bar")));
}
@Test
void messageSourceWithNonStandardBeanNameIsIgnored() {
this.contextRunner.withPropertyValues("spring.messages.basename:test/messages")
this.contextRunner.withPropertyValues("spring.messages.basename=test/messages")
.withUserConfiguration(CustomBeanNameMessageSourceConfiguration.class)
.run((context) -> assertThat(context.getMessage("foo", null, Locale.US)).isEqualTo("bar"));
}

1
spring-boot-project/spring-boot-autoconfigure/src/test/resources/test/common-messages.properties

@ -0,0 +1 @@ @@ -0,0 +1 @@
hello=world

6
spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/internationalization.adoc

@ -14,10 +14,12 @@ The basename of the resource bundle as well as several other attributes can be c @@ -14,10 +14,12 @@ The basename of the resource bundle as well as several other attributes can be c
----
spring:
messages:
basename: "messages,config.i18n.messages"
basename: "messages, config.i18n.messages"
common-messages: "classpath:my-common-messages.properties"
fallback-to-system-locale: false
----
TIP: `spring.messages.basename` supports comma-separated list of locations, either a package qualifier or a resource resolved from the classpath root.
TIP: The configprop:spring.messages.basename[] property supports a list of locations, either a package qualifier or a resource resolved from the classpath root.
The configprop:spring.messages.common-messages[] property supports a list of property file resources.
See javadoc:org.springframework.boot.autoconfigure.context.MessageSourceProperties[] for more supported options.

Loading…
Cancel
Save