Skip to content

Commit

Permalink
Qute type-safe messages - default bundle name strategy #2
Browse files Browse the repository at this point in the history
- follows up on quarkusio#31299
  • Loading branch information
mkouba committed Mar 1, 2023
1 parent fe7b6a2 commit ad802b2
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 53 deletions.
14 changes: 8 additions & 6 deletions docs/src/main/asciidoc/qute-reference.adoc
Expand Up @@ -2498,24 +2498,26 @@ The message bundles can be used at runtime:

The bundle name is defaulted unless it's specified with `@MessageBundle#value()`.
For a top-level class the `msg` value is used by default.
For a nested class the name starts with `msg` followed by an underscore, followed by the simple names of all enclosing classes in the hierarchy (top-level class goes first) seperated by underscores.
For a nested class the name consists of the simple names of all enclosing classes in the hierarchy (top-level class goes first), followed by the simple name of the message bundle interface.
Names are separated by underscores.

For example, the name of the following message bundle will be defaulted to `msg_Index`:
For example, the name of the following message bundle will be defaulted to `Controller_index`:

[source,java]
----
class Index {
class Controller {
@MessageBundle
interface Bundle {
interface index {
@Message("Hello {name}!")
String hello(String name);
String hello(String name); <1>
}
}
----
<1> This message could be used in a template via `{Controller_index:hello(name)}`.

NOTE: The bundle name is also used as a part of the name of a localized file, e.g. `msg_Index` in the `msg_Index_de.properties`.
NOTE: The bundle name is also used as a part of the name of a localized file, e.g. `Controller_index` in the `Controller_index_de.properties`.

==== Bundle Name and Message Keys

Expand Down
Expand Up @@ -144,21 +144,19 @@ List<MessageBundleBuildItem> processBundles(BeanArchiveIndexBuildItem beanArchiv
} else {
// The name starts with the DEFAULT_NAME followed by an underscore, followed by simple names of all
// declaring classes in the hierarchy seperated by underscores
List<String> enclosingNames = new ArrayList<>();
List<String> names = new ArrayList<>();
names.add(DotNames.simpleName(bundleClass));
DotName enclosingName = bundleClass.enclosingClass();
while (enclosingName != null) {
ClassInfo enclosingClass = index.getClassByName(enclosingName);
if (enclosingClass != null) {
enclosingNames.add(DotNames.simpleName(enclosingClass));
names.add(DotNames.simpleName(enclosingClass));
enclosingName = enclosingClass.nestingType() == NestingType.TOP_LEVEL ? null
: enclosingClass.enclosingClass();
}
}
enclosingNames.add(MessageBundle.DEFAULT_NAME);
// Class Bar declares nested class Foo and bundle Baz is declared as nested interface of Foo
// [Foo, Bar, msg] -> [msg, Bar, Foo]
Collections.reverse(enclosingNames);
name = String.join("_", enclosingNames);
Collections.reverse(names);
name = String.join("_", names);
}
LOG.debugf("Message bundle %s: name defaulted to %s", bundleClass, name);
}
Expand Down
@@ -0,0 +1,24 @@
package io.quarkus.qute.deployment.i18n;

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.i18n.Message;
import io.quarkus.qute.i18n.MessageBundle;

public class Controller {

@CheckedTemplate
static class Templates {

static native TemplateInstance index(String name);

}

@MessageBundle
public interface index {

@Message("Hello {name}!")
String hello(String name);
}

}
Expand Up @@ -16,17 +16,17 @@ public class MessageBundleDefaultedNameTest {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(Views.class)
.addClasses(Controller.class)
.addAsResource(new StringAsset(
"{msg_Views_Index:hello(name)}"),
"templates/Index/index.html")
.addAsResource(new StringAsset("hello=Ahoj {name}!"), "messages/msg_Views_Index_cs.properties"));
"{Controller_index:hello(name)}"),
"templates/Controller/index.html")
.addAsResource(new StringAsset("hello=Ahoj {name}!"), "messages/Controller_index_cs.properties"));

@Test
public void testBundle() {
assertEquals("Hello world!",
Views.Index.Templates.index("world").render());
assertEquals("Ahoj svete!", Views.Index.Templates.index("svete")
Controller.Templates.index("world").render());
assertEquals("Ahoj svete!", Controller.Templates.index("svete")
.setAttribute(MessageBundles.ATTRIBUTE_LOCALE, Locale.forLanguageTag("cs")).render());
}

Expand Down
Expand Up @@ -23,7 +23,7 @@ public class MessageBundleLocaleTest {
.withApplicationRoot((jar) -> jar
.addClasses(Messages.class)
.addAsResource(new StringAsset(
"{msg_MessageBundleLocaleTest:helloWorld}"),
"{MessageBundleLocaleTest_Messages:helloWorld}"),
"templates/foo.html"));

@Inject
Expand Down

This file was deleted.

Expand Up @@ -43,18 +43,18 @@
* <p>
* For a top-level class the {@value #DEFAULT_NAME} is used.
* <p>
* For a nested class the name starts with the {@value #DEFAULT_NAME} followed by an undercore, followed by the simple names
* of all enclosing classes in the hierarchy (top-level class goes first) seperated by underscores.
* For a nested class the name consists of the simple names of all enclosing classes in the hierarchy (top-level class goes
* first), followed by the simple name of the message bundle interface. Names are separated by underscores.
*
* For example, the name of the following message bundle will be defaulted to {@code msg_Index} and it could
* be used in a template via <code>{msg_Index:hello(name)}</code>:
* For example, the name of the following message bundle will be defaulted to {@code Controller_index} and it could
* be used in a template via <code>{Controller_index:hello(name)}</code>:
*
* <pre>
* <code>
* class Index {
* class Controller {
*
* &#64;MessageBundle
* interface Bundle {
* interface index {
*
* &#64;Message("Hello {name}!")
* String hello(String name);
Expand Down

0 comments on commit ad802b2

Please sign in to comment.