diff --git a/tck/faces23/localized-composite/pom.xml b/tck/faces23/localized-composite/pom.xml
new file mode 100644
index 00000000000..6fe98bd7c33
--- /dev/null
+++ b/tck/faces23/localized-composite/pom.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ 4.0.0
+
+
+ org.eclipse.ee4j.tck.faces.faces23
+ pom
+ 4.0.0-SNAPSHOT
+
+
+ localized-composite
+ war
+
+ Jakarta Faces TCK ${project.version} - Test - Faces 2.3 - localized-composite
+ Composite .properties l10n files should reflect current locale when using resourceBundleMap.
+
diff --git a/tck/faces23/localized-composite/src/main/java/ee/jakarta/tck/faces/test/composite/LocaleBean.java b/tck/faces23/localized-composite/src/main/java/ee/jakarta/tck/faces/test/composite/LocaleBean.java
new file mode 100644
index 00000000000..5dcaf5ded1f
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/java/ee/jakarta/tck/faces/test/composite/LocaleBean.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2021 Contributors to the Eclipse Foundation.
+ * Copyright (c) 2017, 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+package ee.jakarta.tck.faces.test.composite;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.SessionScoped;
+import jakarta.faces.component.UIViewRoot;
+import jakarta.faces.context.FacesContext;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import jakarta.servlet.http.HttpServletRequest;
+import java.io.Serializable;
+import java.util.Locale;
+
+@Named
+@SessionScoped
+public class LocaleBean implements Serializable {
+
+ @Inject
+ private HttpServletRequest request;
+
+ private Locale locale;
+
+ private String language;
+
+ @PostConstruct
+ public void init() {
+ Locale requestLocale = request.getLocale();
+ setLocale(requestLocale != null ? requestLocale.toString() : "en");
+ }
+
+ private void setLocale(String languageTag) {
+ String[] chunks = languageTag.split("(-|_)");
+ switch (chunks.length) {
+ case 1:
+ locale = new Locale(chunks[0]);
+ break;
+ case 2:
+ locale = new Locale(chunks[0], chunks[1]);
+ break;
+ default:
+ locale = new Locale(chunks[0], chunks[1], chunks[2]);
+ break;
+ }
+ language = locale.toString();
+ UIViewRoot root = FacesContext.getCurrentInstance().getViewRoot();
+ if (root != null) {
+ root.setLocale(locale);
+ }
+ }
+
+ public Locale getLocale() {
+ return locale;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+}
diff --git a/tck/faces23/localized-composite/src/main/resources/locale/messages.properties b/tck/faces23/localized-composite/src/main/resources/locale/messages.properties
new file mode 100644
index 00000000000..5998843460d
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/resources/locale/messages.properties
@@ -0,0 +1,2 @@
+appheader=Application
+buttonvalue=My precious button
diff --git a/tck/faces23/localized-composite/src/main/resources/locale/messages_es.properties b/tck/faces23/localized-composite/src/main/resources/locale/messages_es.properties
new file mode 100644
index 00000000000..f99de0830ff
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/resources/locale/messages_es.properties
@@ -0,0 +1,2 @@
+appheader=Aplicaci\u00f3n
+buttonvalue=Mi precioso bot\u00f3n
diff --git a/tck/faces23/localized-composite/src/main/webapp/WEB-INF/beans.xml b/tck/faces23/localized-composite/src/main/webapp/WEB-INF/beans.xml
new file mode 100644
index 00000000000..802fc5c37ee
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/webapp/WEB-INF/beans.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/tck/faces23/localized-composite/src/main/webapp/WEB-INF/faces-config.xml b/tck/faces23/localized-composite/src/main/webapp/WEB-INF/faces-config.xml
new file mode 100644
index 00000000000..2b4a95e2d28
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/webapp/WEB-INF/faces-config.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ en
+ es
+ pt
+ pt_BR
+ pt_BR_PB
+
+
+ locale.messages
+ msgs
+
+
+
diff --git a/tck/faces23/localized-composite/src/main/webapp/WEB-INF/glassfish-web.xml b/tck/faces23/localized-composite/src/main/webapp/WEB-INF/glassfish-web.xml
new file mode 100644
index 00000000000..cab0a0237b0
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/webapp/WEB-INF/glassfish-web.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+ /localized-composite
+
+
+
+ Keep a copy of the generated servlet class' java code.
+
+
+
diff --git a/tck/faces23/localized-composite/src/main/webapp/WEB-INF/web.xml b/tck/faces23/localized-composite/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..2828f7e3294
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+ jakarta.faces.PROJECT_STAGE
+ ${webapp.projectStage}
+
+
+ jakarta.faces.PARTIAL_STATE_SAVING
+ ${webapp.partialStateSaving}
+
+
+ jakarta.faces.STATE_SAVING_METHOD
+ ${webapp.stateSavingMethod}
+
+
+ jakarta.faces.SERIALIZE_SERVER_STATE
+ ${webapp.serializeServerState}
+
+
+ Faces Servlet
+ jakarta.faces.webapp.FacesServlet
+ 1
+
+
+ Faces Servlet
+ *.xhtml
+
+
+ 30
+
+
+ localized-composite.xhtml
+
+
diff --git a/tck/faces23/localized-composite/src/main/webapp/localized-composite.xhtml b/tck/faces23/localized-composite/src/main/webapp/localized-composite.xhtml
new file mode 100644
index 00000000000..39b8eeec0ef
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/webapp/localized-composite.xhtml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+ JSF 2.3 Localized Composite
+
+
+
+
+
+
+
+
+
+
diff --git a/tck/faces23/localized-composite/src/main/webapp/resources/comps/button.properties b/tck/faces23/localized-composite/src/main/webapp/resources/comps/button.properties
new file mode 100644
index 00000000000..bc61689df26
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/webapp/resources/comps/button.properties
@@ -0,0 +1,2 @@
+btnvalue=Button
+
diff --git a/tck/faces23/localized-composite/src/main/webapp/resources/comps/button.xhtml b/tck/faces23/localized-composite/src/main/webapp/resources/comps/button.xhtml
new file mode 100644
index 00000000000..d95149f9616
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/webapp/resources/comps/button.xhtml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tck/faces23/localized-composite/src/main/webapp/resources/comps/button_es.properties b/tck/faces23/localized-composite/src/main/webapp/resources/comps/button_es.properties
new file mode 100644
index 00000000000..adfe5eca5f0
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/webapp/resources/comps/button_es.properties
@@ -0,0 +1,2 @@
+btnvalue=Bot\u00f3n
+
diff --git a/tck/faces23/localized-composite/src/main/webapp/resources/comps/button_pt.properties b/tck/faces23/localized-composite/src/main/webapp/resources/comps/button_pt.properties
new file mode 100644
index 00000000000..17be4865249
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/webapp/resources/comps/button_pt.properties
@@ -0,0 +1,2 @@
+btnvalue=Accionador
+
diff --git a/tck/faces23/localized-composite/src/main/webapp/resources/comps/button_pt_BR.properties b/tck/faces23/localized-composite/src/main/webapp/resources/comps/button_pt_BR.properties
new file mode 100644
index 00000000000..fecbf754828
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/webapp/resources/comps/button_pt_BR.properties
@@ -0,0 +1,2 @@
+btnvalue=Bot\u00e3o
+
diff --git a/tck/faces23/localized-composite/src/main/webapp/resources/comps/button_pt_BR_PB.properties b/tck/faces23/localized-composite/src/main/webapp/resources/comps/button_pt_BR_PB.properties
new file mode 100644
index 00000000000..0d45d8b8f35
--- /dev/null
+++ b/tck/faces23/localized-composite/src/main/webapp/resources/comps/button_pt_BR_PB.properties
@@ -0,0 +1,2 @@
+btnvalue=Pitoco
+
diff --git a/tck/faces23/localized-composite/src/test/java/ee/jakarta/tck/faces/test/composite/LocalizedCompositeIT.java b/tck/faces23/localized-composite/src/test/java/ee/jakarta/tck/faces/test/composite/LocalizedCompositeIT.java
new file mode 100644
index 00000000000..4b196434587
--- /dev/null
+++ b/tck/faces23/localized-composite/src/test/java/ee/jakarta/tck/faces/test/composite/LocalizedCompositeIT.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2021 Contributors to the Eclipse Foundation.
+ * Copyright (c) 2017, 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package ee.jakarta.tck.faces.test.composite;
+
+import static java.lang.System.getProperty;
+import static org.jboss.shrinkwrap.api.ShrinkWrap.create;
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.net.URL;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.importer.ZipImporter;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlHeading1;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
+
+@RunWith(Arquillian.class)
+public class LocalizedCompositeIT {
+
+ @ArquillianResource
+ private URL webUrl;
+ private WebClient webClient;
+
+ @Deployment(testable = false)
+ public static WebArchive createDeployment() {
+ return create(ZipImporter.class, getProperty("finalName") + ".war")
+ .importFrom(new File("target/" + getProperty("finalName") + ".war"))
+ .as(WebArchive.class);
+ }
+
+ @Before
+ public void setUp() {
+ webClient = new WebClient();
+ webClient.getOptions().setJavaScriptEnabled(true);
+ webClient.setJavaScriptTimeout(120000);
+ }
+
+ /**
+ * @see https://github.com/eclipse-ee4j/mojarra/issues/4097
+ */
+ @Test
+ public void testLocalizedCompositeEn() throws Exception {
+ webClient.addRequestHeader("Accept-Language", "en-US, en;q=0.9, es-ES");
+ HtmlPage page = webClient.getPage(webUrl + "localized-composite.xhtml");
+
+ HtmlHeading1 h1 = (HtmlHeading1) page.getHtmlElementById("header");
+
+ assertEquals("Application", h1.getTextContent());
+
+ HtmlSubmitInput btn1 = (HtmlSubmitInput) page.getHtmlElementById("frm:btn");
+ assertEquals("My precious button", btn1.getAttribute("value"));
+ HtmlSubmitInput btn2 = (HtmlSubmitInput) page.getHtmlElementById("frm:btn1:btn");
+ assertEquals("Button", btn2.getAttribute("value"));
+ }
+
+ @Test
+ public void testLocalizedCompositeEs() throws Exception {
+ webClient.addRequestHeader("Accept-Language", "es-ES");
+ HtmlPage page = webClient.getPage(webUrl + "localized-composite.xhtml");
+
+ HtmlHeading1 h1 = (HtmlHeading1) page.getHtmlElementById("header");
+
+ assertEquals("Aplicación", h1.getTextContent());
+
+ HtmlSubmitInput btn1 = (HtmlSubmitInput) page.getHtmlElementById("frm:btn");
+ assertEquals("Mi precioso botón", btn1.getAttribute("value"));
+ HtmlSubmitInput btn2 = (HtmlSubmitInput) page.getHtmlElementById("frm:btn1:btn");
+ assertEquals("Botón", btn2.getAttribute("value"));
+ }
+
+ @Test
+ public void testLocalizedCompositePtBrPb() throws Exception {
+ webClient.addRequestHeader("Accept-Language", "pt-BR-PB");
+ HtmlPage page = webClient.getPage(webUrl + "localized-composite.xhtml");
+
+ HtmlHeading1 h1 = (HtmlHeading1) page.getHtmlElementById("header");
+
+ assertEquals("Application", h1.getTextContent());
+
+ HtmlSubmitInput btn1 = (HtmlSubmitInput) page.getHtmlElementById("frm:btn");
+ assertEquals("My precious button", btn1.getAttribute("value"));
+ HtmlSubmitInput btn2 = (HtmlSubmitInput) page.getHtmlElementById("frm:btn1:btn");
+ assertEquals("Pitoco", btn2.getAttribute("value"));
+ }
+
+ @After
+ public void tearDown() {
+ webClient.close();
+ }
+
+}