diff --git a/.github/native-tests.json b/.github/native-tests.json index f7cdd076826dc..631bc2fa21730 100644 --- a/.github/native-tests.json +++ b/.github/native-tests.json @@ -105,7 +105,7 @@ { "category": "Misc2", "timeout": 75, - "test-modules": "hibernate-validator, test-extension/tests, logging-gelf, mailer, native-config-profile, locales/all, locales/some", + "test-modules": "hibernate-validator, test-extension/tests, logging-gelf, mailer, native-config-profile, locales/all, locales/some, locales/default", "os-name": "ubuntu-latest" }, { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java index 12c4728038d71..d3fc8972f635e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java @@ -88,7 +88,8 @@ public interface NativeConfig { /** * Defines the user language used for building the native executable. - * It also serves as the default Locale language for the native executable application runtime. + * With GraalVM versions prior to GraalVM for JDK 24 it also serves as the default Locale language for the native executable + * application runtime. * e.g. en or cs as defined by IETF BCP 47 language tags. *

* @@ -100,7 +101,8 @@ public interface NativeConfig { /** * Defines the user country used for building the native executable. - * It also serves as the default Locale country for the native executable application runtime. + * With GraalVM versions prior to GraalVM for JDK 24 it also serves as the default Locale country for the native executable + * application runtime. * e.g. US or FR as defined by ISO 3166-1 alpha-2 codes. *

* diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java index 52b90f72ec07b..130991d72255c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java @@ -196,6 +196,7 @@ public static final class Version implements Comparable { public static final Version VERSION_24_0_0 = new Version("GraalVM 24.0.0", "24.0.0", "22", Distribution.GRAALVM); public static final Version VERSION_24_0_999 = new Version("GraalVM 24.0.999", "24.0.999", "22", Distribution.GRAALVM); public static final Version VERSION_24_1_0 = new Version("GraalVM 24.1.0", "24.1.0", "23", Distribution.GRAALVM); + public static final Version VERSION_24_2_0 = new Version("GraalVM 24.2.0", "24.2.0", "24", Distribution.GRAALVM); /** * The minimum version of GraalVM supported by Quarkus. diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index c1f7f35c94252..7dd1ecdb2eaa8 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -747,6 +747,14 @@ public NativeImageInvokerInfo build() { } } + if (!Locale.getDefault().equals(localesBuildTimeConfig.defaultLocale) + && graalVMVersion.compareTo(GraalVM.Version.VERSION_24_2_0) >= 0) { + log.warn( + "Your application is setting the 'quarkus.default-locale' configuration key. " + + "Starting with GraalVM/Mandrel for JDK 24 this configuration is being ignored and the " + + "default locale is always set at runtime based on the system default locale."); + } + final String userLanguage = LocaleProcessor.nativeImageUserLanguage(nativeConfig, localesBuildTimeConfig); if (!userLanguage.isEmpty()) { nativeImageArgs.add("-J-Duser.language=" + userLanguage); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/LocaleProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/LocaleProcessor.java index 8db1bafb7a109..608458d5fa505 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/LocaleProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/LocaleProcessor.java @@ -124,7 +124,7 @@ public static String nativeImageUserCountry(NativeConfig nativeConfig, LocalesBu } /** - * Additional locales to be included in native-image executable. + * Locales to be included in native-image executable. * * @param nativeConfig * @param localesBuildTimeConfig @@ -139,17 +139,18 @@ public static String nativeImageIncludeLocales(NativeConfig nativeConfig, Locale return "all"; } - // We subtract what we already declare for native-image's user.language or user.country. - // Note the deprecated options still count. - additionalLocales.remove(localesBuildTimeConfig.defaultLocale); + // GraalVM for JDK 24 doesn't include the default locale used at build time. We must explicitly include the + // specified locales - including the build-time locale. + // Note the deprecated options still count and take precedence. if (nativeConfig.userCountry().isPresent() && nativeConfig.userLanguage().isPresent()) { - additionalLocales.remove(new Locale(nativeConfig.userLanguage().get(), nativeConfig.userCountry().get())); + additionalLocales.add(new Locale(nativeConfig.userLanguage().get(), nativeConfig.userCountry().get())); } else if (nativeConfig.userLanguage().isPresent()) { - additionalLocales.remove(new Locale(nativeConfig.userLanguage().get())); + additionalLocales.add(new Locale(nativeConfig.userLanguage().get())); + } else { + additionalLocales.add(localesBuildTimeConfig.defaultLocale); } return additionalLocales.stream() - .filter(l -> !Locale.getDefault().equals(l)) .map(l -> l.getLanguage() + (l.getCountry().isEmpty() ? "" : "-" + l.getCountry())) .collect(Collectors.joining(",")); } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/LocalesBuildTimeConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/LocalesBuildTimeConfig.java index 7ca78d84ceeb0..56c0f62c007e7 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/LocalesBuildTimeConfig.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/LocalesBuildTimeConfig.java @@ -32,8 +32,8 @@ public class LocalesBuildTimeConfig { * A special string "all" is translated as ROOT Locale and then used in native-image * to include all locales. Image size penalty applies. */ - @ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-" - + DEFAULT_COUNTRY, defaultValueDocumentation = "Set containing the build system locale") + @ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-" + DEFAULT_COUNTRY, + defaultValueDocumentation = "Set containing the build system locale") public Set locales; /** @@ -44,8 +44,11 @@ public class LocalesBuildTimeConfig { * For instance, the Hibernate Validator extension makes use of it. *

* Native-image build uses this property to derive {@code user.language} and {@code user.country} for the application's - * runtime. + * runtime. Starting with GraalVM for JDK 24 this option will not result in setting the default runtime locale. + * Instead, the Java configuration {@code user.language} and {@code user.country} will be used as the + * default when running the native image. */ - @ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-" + DEFAULT_COUNTRY, defaultValueDocumentation = "Build system locale") + @ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-" + DEFAULT_COUNTRY, + defaultValueDocumentation = "Build system locale prior to GraalVM for JDK 24, or system default locale at image runtime with GraalVM for JDK 24 and better") public Locale defaultLocale; } diff --git a/integration-tests/locales/default/pom.xml b/integration-tests/locales/default/pom.xml new file mode 100644 index 0000000000000..2d635a1715b6b --- /dev/null +++ b/integration-tests/locales/default/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + + io.quarkus + quarkus-integration-test-locales-parent + 999-SNAPSHOT + + quarkus-integration-test-locales-default + Quarkus - Integration Tests - Locales - Default + + + io.quarkus + quarkus-rest + + + io.quarkus + quarkus-hibernate-validator + + + io.quarkus + quarkus-integration-test-locales-app + ${project.version} + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + io.quarkus + quarkus-rest-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-hibernate-validator-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + src/test/resources + true + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + maven-surefire-plugin + + 1 + false + + + + + diff --git a/integration-tests/locales/default/src/main/java/io/quarkus/locales/it/DefaultLocaleResource.java b/integration-tests/locales/default/src/main/java/io/quarkus/locales/it/DefaultLocaleResource.java new file mode 100644 index 0000000000000..3e01732b91e52 --- /dev/null +++ b/integration-tests/locales/default/src/main/java/io/quarkus/locales/it/DefaultLocaleResource.java @@ -0,0 +1,26 @@ +package io.quarkus.locales.it; + +import jakarta.validation.constraints.Pattern; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import org.jboss.logging.Logger; + +@Path("") +public class DefaultLocaleResource extends LocalesResource { + private static final Logger LOG = Logger.getLogger(DefaultLocaleResource.class); + + // @Pattern validation does nothing when placed in LocalesResource. + @GET + @Path("/hibernate-validator-test-validation-message-locale/{id}/") + @Produces(MediaType.TEXT_PLAIN) + public Response validationMessageLocale( + @Pattern(regexp = "A.*", message = "{pattern.message}") @PathParam("id") String id) { + LOG.infof("Triggering test: id: %s", id); + return Response.ok(id).build(); + } +} diff --git a/integration-tests/locales/default/src/test/java/io/quarkus/locales/it/LocalesIT.java b/integration-tests/locales/default/src/test/java/io/quarkus/locales/it/LocalesIT.java new file mode 100644 index 0000000000000..5f71f6d4e1c2c --- /dev/null +++ b/integration-tests/locales/default/src/test/java/io/quarkus/locales/it/LocalesIT.java @@ -0,0 +1,84 @@ +package io.quarkus.locales.it; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +import org.apache.http.HttpStatus; +import org.jboss.logging.Logger; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import io.quarkus.test.junit.DisableIfBuiltWithGraalVMNewerThan; +import io.quarkus.test.junit.DisableIfBuiltWithGraalVMOlderThan; +import io.quarkus.test.junit.GraalVMVersion; +import io.quarkus.test.junit.QuarkusIntegrationTest; +import io.restassured.RestAssured; + +/** + * For the Native test cases to function, the operating system has to have locales support installed. A barebone system with + * only C.UTF-8 default locale available won't be able to pass the tests. + *

+ * For example, this package satisfies the dependency on a RHEL 9 type of OS: glibc-all-langpacks + */ +@QuarkusIntegrationTest +public class LocalesIT { + + private static final Logger LOG = Logger.getLogger(LocalesIT.class); + + @Test + @DisableIfBuiltWithGraalVMNewerThan(value = GraalVMVersion.GRAALVM_24_1_0) + public void testDefaultLocaleBefore24_2() { + RestAssured.given().when() + .get("/default/de-CH") + .then() + .statusCode(HttpStatus.SC_OK) + /* + * "l-Iżvizzera" is the correct name for Switzerland in Maltese language. + * Maltese is the default language as per quarkus.default-locale=mt-MT. + */ + .body(is("l-Iżvizzera")) + .log().all(); + } + + @Test + @DisableIfBuiltWithGraalVMOlderThan(value = GraalVMVersion.GRAALVM_24_2_0) + public void testDefaultLocaleAfter24_1() { + RestAssured.given().when() + .get("/default/de-CH") + .then() + .statusCode(HttpStatus.SC_OK) + /* + * "l-Iżvizzera" is the correct name for Switzerland in Maltese language. + * Maltese is the default build-time language as per quarkus.default-locale=mt-MT, but not at run-time. + * Note that this test will fail if the default run-time language is Maltese on the test machine, + * this is unfortunate but also unlikely given the small population of Malta. + */ + .body(not("l-Iżvizzera")) + .log().all(); + } + + /** + * @see integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java + */ + @ParameterizedTest + @CsvSource(value = { + // French locale is included, so it's used, because Croatian locale is not included + // and thus its property file ValidationMessages_hr_HR.properties is ignored. + "en-US;q=0.25,hr-HR;q=0.9,fr-FR;q=0.5,uk-UA;q=0.1|La valeur ne correspond pas à l'échantillon", + // Silent fallback to lingua franca. + "invalid string|Value is not in line with the pattern", + // French locale is available and included. + "en-US;q=0.25,hr-HR;q=1,fr-FR;q=0.5|La valeur ne correspond pas à l'échantillon" + }, delimiter = '|') + public void testValidationMessageLocale(String acceptLanguage, String expectedMessage) { + RestAssured.given() + .header("Accept-Language", acceptLanguage) + .when() + .get("/hibernate-validator-test-validation-message-locale/1") + .then() + .body(containsString(expectedMessage)); + } + +} diff --git a/integration-tests/locales/default/src/test/java/io/quarkus/locales/it/LocalesTest.java b/integration-tests/locales/default/src/test/java/io/quarkus/locales/it/LocalesTest.java new file mode 100644 index 0000000000000..bd42cd4ae7fe7 --- /dev/null +++ b/integration-tests/locales/default/src/test/java/io/quarkus/locales/it/LocalesTest.java @@ -0,0 +1,7 @@ +package io.quarkus.locales.it; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class LocalesTest { +} diff --git a/integration-tests/locales/default/src/test/resources/ValidationMessages.properties b/integration-tests/locales/default/src/test/resources/ValidationMessages.properties new file mode 100644 index 0000000000000..48a3bbf23dce1 --- /dev/null +++ b/integration-tests/locales/default/src/test/resources/ValidationMessages.properties @@ -0,0 +1 @@ +pattern.message=Value is not in line with the pattern diff --git a/integration-tests/locales/default/src/test/resources/ValidationMessages_fr_FR.properties b/integration-tests/locales/default/src/test/resources/ValidationMessages_fr_FR.properties new file mode 100644 index 0000000000000..c9a6e5d71d6f0 --- /dev/null +++ b/integration-tests/locales/default/src/test/resources/ValidationMessages_fr_FR.properties @@ -0,0 +1 @@ +pattern.message=La valeur ne correspond pas à l'échantillon diff --git a/integration-tests/locales/default/src/test/resources/ValidationMessages_hr_HR.properties b/integration-tests/locales/default/src/test/resources/ValidationMessages_hr_HR.properties new file mode 100644 index 0000000000000..ae2e444a98105 --- /dev/null +++ b/integration-tests/locales/default/src/test/resources/ValidationMessages_hr_HR.properties @@ -0,0 +1 @@ +pattern.message=Vrijednost ne zadovoljava uzorak diff --git a/integration-tests/locales/default/src/test/resources/application.properties b/integration-tests/locales/default/src/test/resources/application.properties new file mode 100644 index 0000000000000..d0d560bcb8336 --- /dev/null +++ b/integration-tests/locales/default/src/test/resources/application.properties @@ -0,0 +1,4 @@ +quarkus.locales=de,fr-FR,ja,uk-UA +# Note that quarkus.native.user-language is deprecated and solely quarkus.default-locale should be +# used in your application properties. This test uses it only to verify compatibility. +quarkus.default-locale=mt-MT diff --git a/integration-tests/locales/pom.xml b/integration-tests/locales/pom.xml index 0478b1166eef5..08dc1763e4ba0 100644 --- a/integration-tests/locales/pom.xml +++ b/integration-tests/locales/pom.xml @@ -14,6 +14,7 @@ app all + default some diff --git a/integration-tests/locales/some/src/test/java/io/quarkus/locales/it/LocalesIT.java b/integration-tests/locales/some/src/test/java/io/quarkus/locales/it/LocalesIT.java index c0958e0a6a98b..dab72bc4f499b 100644 --- a/integration-tests/locales/some/src/test/java/io/quarkus/locales/it/LocalesIT.java +++ b/integration-tests/locales/some/src/test/java/io/quarkus/locales/it/LocalesIT.java @@ -11,6 +11,7 @@ import org.junit.jupiter.params.provider.CsvSource; import io.quarkus.test.junit.DisableIfBuiltWithGraalVMNewerThan; +import io.quarkus.test.junit.DisableIfBuiltWithGraalVMOlderThan; import io.quarkus.test.junit.GraalVMVersion; import io.quarkus.test.junit.QuarkusIntegrationTest; import io.restassured.RestAssured; @@ -82,11 +83,9 @@ public void testTimeZones(String zone, String language, String name) { .log().all(); } - // Disable test with GraalVM 24.2 for JDK 24 and later till we reach a conclusion in - // https://github.com/quarkusio/quarkus/discussions/43533 @Test @DisableIfBuiltWithGraalVMNewerThan(value = GraalVMVersion.GRAALVM_24_1_0) - public void testDefaultLocale() { + public void testDefaultLocaleBefore24_2() { RestAssured.given().when() .get("/default/de-CH") .then() @@ -99,6 +98,21 @@ public void testDefaultLocale() { .log().all(); } + @Test + @DisableIfBuiltWithGraalVMOlderThan(value = GraalVMVersion.GRAALVM_24_2_0) + public void testDefaultLocaleAfter24_1() { + RestAssured.given().when() + .get("/default/de-CH") + .then() + .statusCode(HttpStatus.SC_OK) + /* + * "Schweiz" is the correct name for Switzerland in German. + * German is the default language as per the `quarkus.test.arg-line` in application.properties. + */ + .body(is("Schweiz")) + .log().all(); + } + @Test public void testMissingLocaleSorryItaly() { RestAssured.given().when() diff --git a/integration-tests/locales/some/src/test/resources/application.properties b/integration-tests/locales/some/src/test/resources/application.properties index 4d3a429e5788c..4617c6481b6d1 100644 --- a/integration-tests/locales/some/src/test/resources/application.properties +++ b/integration-tests/locales/some/src/test/resources/application.properties @@ -3,3 +3,4 @@ quarkus.locales=de,fr-FR,ja,uk-UA # used in your application properties. This test uses it only to verify compatibility. quarkus.native.user-language=cs quarkus.default-locale=en-US +quarkus.test.arg-line=-Duser.language=de \ No newline at end of file diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/GraalVMVersion.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/GraalVMVersion.java index 7110ce6c275b3..0b06c693126cc 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/GraalVMVersion.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/GraalVMVersion.java @@ -7,7 +7,8 @@ public enum GraalVMVersion { GRAALVM_23_1_3(GraalVM.Version.VERSION_23_1_3), GRAALVM_24_0_0(GraalVM.Version.VERSION_24_0_0), GRAALVM_24_0_999(GraalVM.Version.VERSION_24_0_999), - GRAALVM_24_1_0(GraalVM.Version.VERSION_24_1_0); + GRAALVM_24_1_0(GraalVM.Version.VERSION_24_1_0), + GRAALVM_24_2_0(GraalVM.Version.VERSION_24_2_0); private final GraalVM.Version version;