Skip to content

Commit 9fcb390

Browse files
authored
Merge pull request #46280 from mkouba/issue-46259
Qute message bundles: add Message#defaultValue()
2 parents 269984f + fc03e4f commit 9fcb390

File tree

4 files changed

+134
-10
lines changed

4 files changed

+134
-10
lines changed

docs/src/main/asciidoc/qute-reference.adoc

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3082,8 +3082,10 @@ TIP: There is also <<convenient-annotation-for-enums,`@TemplateEnum`>> - a conve
30823082

30833083
==== Message Templates
30843084

3085-
Every method of a message bundle interface must define a message template. The value is normally defined by `io.quarkus.qute.i18n.Message#value()`,
3086-
but for convenience, there is also an option to define the value in a localized file.
3085+
Every method of a message bundle interface must define a message template.
3086+
The value is normally defined by `io.quarkus.qute.i18n.Message#value()`, but for convenience, there is also an option to define the value in a localized file.
3087+
Message templates are validated during the build.
3088+
If a missing message template is detected, an exception is thrown and the build fails.
30873089

30883090
.Example of the Message Bundle Interface without the value
30893091
[source,java]
@@ -3114,8 +3116,23 @@ goodbye=Best regards, {name} <1>
31143116
----
31153117
<1> The value is ignored as `io.quarkus.qute.i18n.Message#value()` is always prioritized.
31163118

3117-
Message templates are validated during the build. If a missing message template is detected, an exception is thrown and build fails.
3119+
It is also possible to define a _default message template_.
3120+
The default template is only used if the `Message#value()` is not specified and no relevant message template is defined in a localized file.
31183121

3122+
.Example of the Message Bundle Interface with a default value
3123+
[source,java]
3124+
----
3125+
import io.quarkus.qute.i18n.Message;
3126+
import io.quarkus.qute.i18n.MessageBundle;
3127+
3128+
@MessageBundle
3129+
public interface AppMessages {
3130+
3131+
@Message(defaultValue = "Goodbye {name}!") <1>
3132+
String goodbye(String name);
3133+
}
3134+
----
3135+
<1> The annotation value is only used if no message template is defined in a localized file.
31193136

31203137
=== Configuration Reference
31213138

extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,7 @@ private Map<String, String> getLocalizedFileKeyToTemplate(MessageBundleBuildItem
812812
messageAnnotation = AnnotationInstance.builder(Names.MESSAGE).value(Message.DEFAULT_VALUE)
813813
.add("name", Message.DEFAULT_NAME).build();
814814
}
815-
return getMessageAnnotationValue(messageAnnotation) != null;
815+
return getMessageAnnotationValue(messageAnnotation, false) != null;
816816
})
817817
.map(MethodInfo::name)
818818
.forEach(keyToTemplate::remove);
@@ -1013,13 +1013,13 @@ private String generateImplementation(MessageBundleBuildItem bundle, ClassInfo d
10131013
boolean generatedTemplate = false;
10141014
String messageTemplate = messageTemplates.get(method.name());
10151015
if (messageTemplate == null) {
1016-
messageTemplate = getMessageAnnotationValue(messageAnnotation);
1016+
messageTemplate = getMessageAnnotationValue(messageAnnotation, true);
10171017
}
10181018

10191019
if (messageTemplate == null && defaultBundleInterface != null) {
10201020
// method is annotated with @Message without value() -> fallback to default locale
10211021
messageTemplate = getMessageAnnotationValue((defaultBundleInterface.method(method.name(),
1022-
method.parameterTypes().toArray(new Type[] {}))).annotation(Names.MESSAGE));
1022+
method.parameterTypes().toArray(new Type[] {}))).annotation(Names.MESSAGE), true);
10231023
}
10241024

10251025
// We need some special handling for enum message bundle methods
@@ -1215,11 +1215,19 @@ private void generateEnumConstantMessageMethod(ClassCreator bundleCreator, Strin
12151215
/**
12161216
* @return {@link Message#value()} if value was provided
12171217
*/
1218-
private String getMessageAnnotationValue(AnnotationInstance messageAnnotation) {
1218+
private String getMessageAnnotationValue(AnnotationInstance messageAnnotation, boolean useDefault) {
12191219
var messageValue = messageAnnotation.value();
12201220
if (messageValue == null || messageValue.asString().equals(Message.DEFAULT_VALUE)) {
12211221
// no value was provided in annotation
1222-
return null;
1222+
if (useDefault) {
1223+
var defaultMessageValue = messageAnnotation.value("defaultValue");
1224+
if (defaultMessageValue == null || defaultMessageValue.asString().equals(Message.DEFAULT_VALUE)) {
1225+
return null;
1226+
}
1227+
return defaultMessageValue.asString();
1228+
} else {
1229+
return null;
1230+
}
12231231
}
12241232
return messageValue.asString();
12251233
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package io.quarkus.qute.deployment.i18n;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import jakarta.inject.Inject;
6+
7+
import org.jboss.shrinkwrap.api.asset.StringAsset;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.extension.RegisterExtension;
10+
11+
import io.quarkus.qute.Template;
12+
import io.quarkus.qute.i18n.Localized;
13+
import io.quarkus.qute.i18n.Message;
14+
import io.quarkus.qute.i18n.MessageBundle;
15+
import io.quarkus.test.QuarkusUnitTest;
16+
17+
public class MessageDefaultValueTest {
18+
19+
@RegisterExtension
20+
static final QuarkusUnitTest config = new QuarkusUnitTest()
21+
.withApplicationRoot(root -> root
22+
.addClasses(Messages.class)
23+
.addAsResource(new StringAsset("""
24+
alpha=Hi {foo}!
25+
delta=Hey {foo}!
26+
"""), "messages/msg_en.properties")
27+
.addAsResource(new StringAsset("""
28+
alpha=Ahoj {foo}!
29+
delta=Hej {foo}!
30+
"""), "messages/msg_cs.properties")
31+
32+
.addAsResource(new StringAsset(
33+
"{msg:alpha('baz')}::{msg:bravo('baz')}::{msg:charlie('baz')}"),
34+
"templates/foo.html")
35+
.addAsResource(new StringAsset(
36+
"{msg:delta('baz')}::{msg:echo('baz')}"),
37+
"templates/bar.html"))
38+
.overrideConfigKey("quarkus.default-locale", "en");
39+
40+
@Inject
41+
Template foo;
42+
43+
@Inject
44+
Template bar;
45+
46+
@Test
47+
public void testMessages() {
48+
assertEquals("Hi baz!::Bravo baz!::Hey baz!", foo.instance().setLocale("en").render());
49+
assertEquals("Hej baz!::Echo cs baz!", bar.instance().setLocale("cs").render());
50+
}
51+
52+
@MessageBundle("msg")
53+
public interface Messages {
54+
55+
// localized file wins
56+
@Message(defaultValue = "Alpha {foo}!")
57+
String alpha(String foo);
58+
59+
// defaultValue is used
60+
@Message(defaultValue = "Bravo {foo}!")
61+
String bravo(String foo);
62+
63+
// value() wins
64+
@Message(value = "Hey {foo}!", defaultValue = "Charlie {foo}!")
65+
String charlie(String foo);
66+
67+
// msg_cs.properties wins
68+
@Message(defaultValue = "Delta {foo}!")
69+
String delta(String foo);
70+
71+
// CsMessages#echo() wins
72+
@Message(defaultValue = "Echo {foo}!")
73+
String echo(String foo);
74+
75+
}
76+
77+
@Localized("cs")
78+
public interface CsMessages extends Messages {
79+
80+
// msg_cs.properties wins
81+
@Message(defaultValue = "Delta cs {foo}!")
82+
@Override
83+
String delta(String foo);
84+
85+
@Message(defaultValue = "Echo cs {foo}!")
86+
@Override
87+
String echo(String foo);
88+
89+
}
90+
91+
}

extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/Message.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,21 @@
9797

9898
/**
9999
* This value has higher priority over a message template specified in a localized file, and it's
100-
* considered a good practice to specify it. In case the value is not provided and there is no
101-
* match in the localized file too, the build fails.
100+
* considered a good practice to specify it. In case the value is not provided, there is no
101+
* match in the localized file and the {@link #defaultValue()} is not specified, the build fails.
102102
* <p>
103103
* There is a convenient way to localize enums. See the javadoc of {@link Message}.
104104
*
105105
* @return the message template
106106
*/
107107
String value() default DEFAULT_VALUE;
108108

109+
/**
110+
* The default template is only used if {@link #value()} is not specified and a message template is not defined in a
111+
* localized file.
112+
*
113+
* @return the default message template
114+
*/
115+
String defaultValue() default DEFAULT_VALUE;
116+
109117
}

0 commit comments

Comments
 (0)