Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qute message bundles: add Message#defaultValue() #46280

Merged
merged 1 commit into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions docs/src/main/asciidoc/qute-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3082,8 +3082,10 @@ TIP: There is also <<convenient-annotation-for-enums,`@TemplateEnum`>> - a conve

==== Message Templates

Every method of a message bundle interface must define a message template. 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.
Every method of a message bundle interface must define a message template.
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.
Message templates are validated during the build.
If a missing message template is detected, an exception is thrown and the build fails.

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

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

.Example of the Message Bundle Interface with a default value
[source,java]
----
import io.quarkus.qute.i18n.Message;
import io.quarkus.qute.i18n.MessageBundle;

@MessageBundle
public interface AppMessages {

@Message(defaultValue = "Goodbye {name}!") <1>
String goodbye(String name);
}
----
<1> The annotation value is only used if no message template is defined in a localized file.

=== Configuration Reference

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,7 @@ private Map<String, String> getLocalizedFileKeyToTemplate(MessageBundleBuildItem
messageAnnotation = AnnotationInstance.builder(Names.MESSAGE).value(Message.DEFAULT_VALUE)
.add("name", Message.DEFAULT_NAME).build();
}
return getMessageAnnotationValue(messageAnnotation) != null;
return getMessageAnnotationValue(messageAnnotation, false) != null;
})
.map(MethodInfo::name)
.forEach(keyToTemplate::remove);
Expand Down Expand Up @@ -1013,13 +1013,13 @@ private String generateImplementation(MessageBundleBuildItem bundle, ClassInfo d
boolean generatedTemplate = false;
String messageTemplate = messageTemplates.get(method.name());
if (messageTemplate == null) {
messageTemplate = getMessageAnnotationValue(messageAnnotation);
messageTemplate = getMessageAnnotationValue(messageAnnotation, true);
}

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

// We need some special handling for enum message bundle methods
Expand Down Expand Up @@ -1215,11 +1215,19 @@ private void generateEnumConstantMessageMethod(ClassCreator bundleCreator, Strin
/**
* @return {@link Message#value()} if value was provided
*/
private String getMessageAnnotationValue(AnnotationInstance messageAnnotation) {
private String getMessageAnnotationValue(AnnotationInstance messageAnnotation, boolean useDefault) {
var messageValue = messageAnnotation.value();
if (messageValue == null || messageValue.asString().equals(Message.DEFAULT_VALUE)) {
// no value was provided in annotation
return null;
if (useDefault) {
var defaultMessageValue = messageAnnotation.value("defaultValue");
if (defaultMessageValue == null || defaultMessageValue.asString().equals(Message.DEFAULT_VALUE)) {
return null;
}
return defaultMessageValue.asString();
} else {
return null;
}
}
return messageValue.asString();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package io.quarkus.qute.deployment.i18n;

import static org.junit.jupiter.api.Assertions.assertEquals;

import jakarta.inject.Inject;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.qute.Template;
import io.quarkus.qute.i18n.Localized;
import io.quarkus.qute.i18n.Message;
import io.quarkus.qute.i18n.MessageBundle;
import io.quarkus.test.QuarkusUnitTest;

public class MessageDefaultValueTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot(root -> root
.addClasses(Messages.class)
.addAsResource(new StringAsset("""
alpha=Hi {foo}!
delta=Hey {foo}!
"""), "messages/msg_en.properties")
.addAsResource(new StringAsset("""
alpha=Ahoj {foo}!
delta=Hej {foo}!
"""), "messages/msg_cs.properties")

.addAsResource(new StringAsset(
"{msg:alpha('baz')}::{msg:bravo('baz')}::{msg:charlie('baz')}"),
"templates/foo.html")
.addAsResource(new StringAsset(
"{msg:delta('baz')}::{msg:echo('baz')}"),
"templates/bar.html"))
.overrideConfigKey("quarkus.default-locale", "en");

@Inject
Template foo;

@Inject
Template bar;

@Test
public void testMessages() {
assertEquals("Hi baz!::Bravo baz!::Hey baz!", foo.instance().setLocale("en").render());
assertEquals("Hej baz!::Echo cs baz!", bar.instance().setLocale("cs").render());
}

@MessageBundle("msg")
public interface Messages {

// localized file wins
@Message(defaultValue = "Alpha {foo}!")
String alpha(String foo);

// defaultValue is used
@Message(defaultValue = "Bravo {foo}!")
String bravo(String foo);

// value() wins
@Message(value = "Hey {foo}!", defaultValue = "Charlie {foo}!")
String charlie(String foo);

// msg_cs.properties wins
@Message(defaultValue = "Delta {foo}!")
String delta(String foo);

// CsMessages#echo() wins
@Message(defaultValue = "Echo {foo}!")
String echo(String foo);

}

@Localized("cs")
public interface CsMessages extends Messages {

// msg_cs.properties wins
@Message(defaultValue = "Delta cs {foo}!")
@Override
String delta(String foo);

@Message(defaultValue = "Echo cs {foo}!")
@Override
String echo(String foo);

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,21 @@

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

/**
* The default template is only used if {@link #value()} is not specified and a message template is not defined in a
* localized file.
*
* @return the default message template
*/
String defaultValue() default DEFAULT_VALUE;

}
Loading