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 - use locale from the selected variant if needed #14801

Merged
merged 1 commit into from
Feb 5, 2021
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
41 changes: 41 additions & 0 deletions docs/src/main/asciidoc/qute-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1657,6 +1657,47 @@ hello_name=Hallo {name}! <1> <2>
<1> Each line in a localized file represents a message template.
<2> Keys and values are separated by the equals sign.

Once we have the localized bundles defined we need a way to _select_ a correct bundle.
If you use a message bundle expression in a template you'll have to specify the `locale` attribute of a template instance.

.`locale` Attribute Example
[source,java]
----
@Singleton
public class MyBean {

@Inject
Template hello;

String render() {
return hello.instance().setAttribute("locale", Locale.forLanguageTag("cs")).render(); <1>
}
}
----
<1> You can set a `Locale` instance or a locale tag string (IETF).


NOTE: When using <<quarkus-resteasy-qute,resteasy_integration>> the `locale` attribute is derived from the the `Accept-Language` header if not set by a user.

The `@Localized` qualifier can be used to inject a localized message bundle interface.

.Injected Localized Message Bundle Example
[source,java]
----
@Singleton
public class MyBean {

@Localized("cs") <1>
AppMessages msg;

String render() {
return msg.hello_name("Jachym");
}
}
----
<1> The annotation value is a locale tag string (IETF).


=== Configuration Reference

include::{generated-dir}/config/quarkus-qute.adoc[leveloffset=+1, opts=optional]
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import io.quarkus.qute.NamespaceResolver;
import io.quarkus.qute.Resolver;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Variant;
import io.quarkus.qute.runtime.MessageBundleRecorder.BundleContext;

public final class MessageBundles {
Expand Down Expand Up @@ -81,10 +83,21 @@ static void setupNamespaceResolvers(@Observes EngineBuilder builder, BundleConte
public CompletionStage<Object> resolve(EvalContext context) {
Object locale = context.getAttribute(ATTRIBUTE_LOCALE);
if (locale == null) {
return defaultResolver.resolve(context);
Object selectedVariant = context.getAttribute(TemplateInstance.SELECTED_VARIANT);
if (selectedVariant != null) {
locale = ((Variant) selectedVariant).getLocale();
}
if (locale == null) {
return defaultResolver.resolve(context);
}
}
// First try the exact match
Resolver localeResolver = interfaces
.get(locale instanceof Locale ? ((Locale) locale).toLanguageTag() : locale.toString());
if (localeResolver == null && locale instanceof Locale) {
// Next try the language
localeResolver = interfaces.get(((Locale) locale).getLanguage());
}
return localeResolver != null ? localeResolver.resolve(context) : defaultResolver.resolve(context);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
Expand Down Expand Up @@ -184,7 +185,8 @@ private Template template() {
Variant selected = (Variant) getAttribute(TemplateInstance.SELECTED_VARIANT);
String id;
if (selected != null) {
id = variants.variantToTemplate.get(selected);
// Currently, we only use the content type to match the template
id = variants.getId(selected.getContentType());
if (id == null) {
id = variants.defaultTemplate;
}
Expand All @@ -206,14 +208,23 @@ public TemplateVariants(Map<Variant, String> variants, String defaultTemplate) {
this.defaultTemplate = defaultTemplate;
}

String getId(String contentType) {
for (Entry<Variant, String> entry : variantToTemplate.entrySet()) {
if (entry.getKey().getContentType().equals(contentType)) {
return entry.getValue();
}
}
return null;
}

@Override
public String toString() {
return "TemplateVariants{default=" + defaultTemplate + ", variants=" + variantToTemplate + "}";
}
}

private static Map<Variant, String> initVariants(String base, List<String> availableVariants, ContentTypes contentTypes) {
Map<Variant, String> map = new HashMap<>();
Map<Variant, String> map = new LinkedHashMap<>();
for (String path : availableVariants) {
if (!base.equals(path)) {
map.put(new Variant(null, contentTypes.getContentType(path), null), path);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.quarkus.qute.resteasy.deployment;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;

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

import io.quarkus.test.QuarkusUnitTest;

public class MessageBundleLocaleFromVariantTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(AppMessages.class, AppMessageHelloResource.class)
.addAsResource(new StringAsset(
"{msg:hello_name('Georg')}"),
"templates/hello.html")
.addAsResource(new StringAsset(
"hello=Hallo Welt!\nhello_name=Hallo {name}!"),
"messages/msg_de.properties"));

@Test
public void testMessageBundles() {
given().header("Accept-Language", "de-DE").when().get("/hello").then().body(is("Hallo Georg!"));

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class VariantTemplateTest {
@Test
public void testVariant() {
given().when().accept("text/plain").get("/item/10").then().body(Matchers.is("Item foo: 10"));
given().when().get("/item/20").then().body(Matchers.is("<html><body>Item foo: 20</body></html>"));
given().when().accept("text/html").get("/item/20").then().body(Matchers.is("<html><body>Item foo: 20</body></html>"));
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package io.quarkus.resteasy.qute.runtime;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Locale;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
Expand All @@ -18,6 +19,7 @@
@Provider
public class TemplateResponseFilter implements ContainerResponseFilter {

@SuppressWarnings("unchecked")
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
Expand All @@ -30,24 +32,30 @@ public void filter(ContainerRequestContext requestContext, ContainerResponseCont
TemplateInstance instance = (TemplateInstance) entity;
Object variantsAttr = instance.getAttribute(TemplateInstance.VARIANTS);
if (variantsAttr != null) {
@SuppressWarnings("unchecked")
List<Variant> variants = (List<Variant>) variantsAttr;
List<javax.ws.rs.core.Variant> variants = new ArrayList<>();
for (Variant variant : (List<Variant>) variantsAttr) {
variants.add(new javax.ws.rs.core.Variant(MediaType.valueOf(variant.getMediaType()), variant.getLocale(),
variant.getEncoding()));
}
javax.ws.rs.core.Variant selected = requestContext.getRequest()
.selectVariant(variants.stream()
.map(v -> new javax.ws.rs.core.Variant(MediaType.valueOf(v.getMediaType()), v.getLocale(),
v.getEncoding()))
.collect(Collectors.toList()));
.selectVariant(variants);

if (selected != null) {
Locale selectedLocale = selected.getLanguage();
if (selectedLocale == null) {
List<Locale> acceptableLocales = requestContext.getAcceptableLanguages();
if (!acceptableLocales.isEmpty()) {
selectedLocale = acceptableLocales.get(0);
}
}
instance.setAttribute(TemplateInstance.SELECTED_VARIANT,
new Variant(selected.getLanguage(), selected.getMediaType().toString(), selected.getEncoding()));
new Variant(selectedLocale, selected.getMediaType().toString(), selected.getEncoding()));
mediaType = selected.getMediaType();
} else {
// TODO we should use the default
mediaType = null;
mediaType = responseContext.getMediaType();
}
} else {
// TODO how to get media type from non-variant templates?
mediaType = null;
mediaType = responseContext.getMediaType();
}

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class VariantTemplateTest {
@Test
public void testVariant() {
given().when().accept("text/plain").get("/item/10").then().body(Matchers.is("Item foo: 10"));
given().when().get("/item/20").then().body(Matchers.is("<html><body>Item foo: 20</body></html>"));
given().when().accept("text/html").get("/item/20").then().body(Matchers.is("<html><body>Item foo: 20</body></html>"));
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package io.quarkus.resteasy.reactive.qute.runtime;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Locale;

import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.core.MediaType;
Expand All @@ -15,6 +16,7 @@

public class TemplateResponseFilter {

@SuppressWarnings("unchecked")
@ServerResponseFilter
public Uni<Void> filter(ResteasyReactiveContainerRequestContext requestContext, ContainerResponseContext responseContext) {
Object entity = responseContext.getEntity();
Expand All @@ -26,16 +28,24 @@ public Uni<Void> filter(ResteasyReactiveContainerRequestContext requestContext,
TemplateInstance instance = (TemplateInstance) entity;
Object variantsAttr = instance.getAttribute(TemplateInstance.VARIANTS);
if (variantsAttr != null) {
@SuppressWarnings("unchecked")
List<Variant> variants = (List<Variant>) variantsAttr;
List<javax.ws.rs.core.Variant> variants = new ArrayList<>();
for (Variant variant : (List<Variant>) variantsAttr) {
variants.add(new javax.ws.rs.core.Variant(MediaType.valueOf(variant.getMediaType()), variant.getLocale(),
variant.getEncoding()));
}
javax.ws.rs.core.Variant selected = requestContext.getRequest()
.selectVariant(variants.stream()
.map(v -> new javax.ws.rs.core.Variant(MediaType.valueOf(v.getMediaType()), v.getLocale(),
v.getEncoding()))
.collect(Collectors.toList()));
.selectVariant(variants);

if (selected != null) {
Locale selectedLocale = selected.getLanguage();
if (selectedLocale == null) {
List<Locale> acceptableLocales = requestContext.getAcceptableLanguages();
if (!acceptableLocales.isEmpty()) {
selectedLocale = acceptableLocales.get(0);
}
}
instance.setAttribute(TemplateInstance.SELECTED_VARIANT,
new Variant(selected.getLanguage(), selected.getMediaType().toString(), selected.getEncoding()));
new Variant(selectedLocale, selected.getMediaType().toString(), selected.getEncoding()));
mediaType = selected.getMediaType();
} else {
mediaType = responseContext.getMediaType();
Expand Down