From 8e4329a22fa822eaff548c40fedcfb92ed50c972 Mon Sep 17 00:00:00 2001 From: czp13 <61667986+czp13@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:18:33 +0200 Subject: [PATCH] fix/feature/optimization: use lang attribute in the html tag (#16410) (CP:24.0) (may not need) (#16502) * fix/feature/optimization: use lang attribute in the html tag (#16410) Fix and feature add language to HTML tags dynamically: 0. It is checking if it is already added (if not, then it will try to add it based on the following rules: 1. trying to get the locale from the UI 2. if there is an I18N provider then the first locale will be used 3. last option is to fallback to the Locale.getDefault() This is the same logic as is used in the Component class as well, to have consistency within the application. Covering tests and optimization, refactoring happened and added. Thanks to @knoobie and @tepi for reviews, ideas, and optimizations. Fixes # (issue): * Fix build error (comment format :/, hopefully) --- .../com/vaadin/flow/component/Component.java | 32 +++++----------- .../com/vaadin/flow/internal/LocaleUtil.java | 38 ++++++++++++++++++- .../com/vaadin/flow/server/VaadinService.java | 13 ++----- .../IndexHtmlRequestHandler.java | 18 ++++++--- .../JavaScriptBootstrapHandler.java | 8 ++-- .../vaadin/flow/server/frontend/index.html | 2 +- .../IndexHtmlRequestHandlerTest.java | 11 ++++++ .../META-INF/VAADIN/webapp/index.html | 2 +- .../src/test/resources/frontend/index.html | 2 +- .../frontend/index.html | 2 +- flow-tests/test-ccdm/frontend/index.html | 2 +- .../test-theme-dev-bundle/frontend/index.html | 2 +- .../vite-basics/frontend/index.html | 2 +- .../frontend/index.html | 2 +- .../vite-embedded/frontend/index.html | 2 +- .../vite-production/frontend/index.html | 2 +- .../src/main/webapp/offline.html | 2 +- .../vite-pwa-production/frontend/index.html | 2 +- .../vite-pwa/frontend/index.html | 2 +- .../test-pwa/src/main/webapp/offline.html | 2 +- .../src/main/webapp/offline.html | 2 +- flow-tests/test-themes/frontend/index.html | 2 +- 22 files changed, 94 insertions(+), 58 deletions(-) diff --git a/flow-server/src/main/java/com/vaadin/flow/component/Component.java b/flow-server/src/main/java/com/vaadin/flow/component/Component.java index 89a9862bdab..c8f97836dee 100644 --- a/flow-server/src/main/java/com/vaadin/flow/component/Component.java +++ b/flow-server/src/main/java/com/vaadin/flow/component/Component.java @@ -20,7 +20,6 @@ import java.util.Collections; import java.util.Locale; import java.util.Optional; -import java.util.function.Supplier; import java.util.stream.Stream; import java.util.stream.Stream.Builder; @@ -33,9 +32,9 @@ import com.vaadin.flow.dom.ShadowRoot; import com.vaadin.flow.i18n.I18NProvider; import com.vaadin.flow.internal.AnnotationReader; +import com.vaadin.flow.internal.LocaleUtil; import com.vaadin.flow.internal.nodefeature.ElementData; import com.vaadin.flow.server.Attributes; -import com.vaadin.flow.server.VaadinService; import com.vaadin.flow.shared.Registration; /** @@ -636,10 +635,11 @@ protected boolean isTemplateMapped() { * null) */ public String getTranslation(String key, Object... params) { - final Optional i18NProvider = getI18NProvider(); + final Optional i18NProvider = LocaleUtil + .getI18NProvider(); return i18NProvider .map(i18n -> i18n.getTranslation(key, - getLocale(() -> i18NProvider), params)) + LocaleUtil.getLocale(() -> i18NProvider), params)) .orElseGet(() -> "!{" + key + "}!"); } @@ -660,10 +660,11 @@ public String getTranslation(String key, Object... params) { * null) */ public String getTranslation(Object key, Object... params) { - final Optional i18NProvider = getI18NProvider(); + final Optional i18NProvider = LocaleUtil + .getI18NProvider(); return i18NProvider .map(i18n -> i18n.getTranslation(key, - getLocale(() -> i18NProvider), params)) + LocaleUtil.getLocale(() -> i18NProvider), params)) .orElseGet(() -> "!{" + key + "}!"); } @@ -686,7 +687,7 @@ public String getTranslation(Object key, Object... params) { */ @Deprecated public String getTranslation(String key, Locale locale, Object... params) { - return getI18NProvider() + return LocaleUtil.getI18NProvider() .map(i18n -> i18n.getTranslation(key, locale, params)) .orElseGet(() -> "!{" + key + "}!"); } @@ -710,7 +711,7 @@ public String getTranslation(String key, Locale locale, Object... params) { */ @Deprecated public String getTranslation(Object key, Locale locale, Object... params) { - return getI18NProvider() + return LocaleUtil.getI18NProvider() .map(i18n -> i18n.getTranslation(key, locale, params)) .orElseGet(() -> "!{" + key + "}!"); } @@ -753,11 +754,6 @@ public String getTranslation(Locale locale, Object key, Object... params) { return getTranslation(key, locale, params); } - private Optional getI18NProvider() { - return Optional.ofNullable( - VaadinService.getCurrent().getInstantiator().getI18NProvider()); - } - /** * Gets the locale for this component. *

@@ -769,15 +765,7 @@ private Optional getI18NProvider() { * @return the component locale */ protected Locale getLocale() { - return getLocale(() -> getI18NProvider()); - } - - private Locale getLocale(Supplier> i18NProvider) { - return Optional.ofNullable(UI.getCurrent()).map(UI::getLocale) - .or(() -> i18NProvider.get() - .map(I18NProvider::getProvidedLocales) - .flatMap(locales -> locales.stream().findFirst())) - .orElseGet(Locale::getDefault); + return LocaleUtil.getLocale(LocaleUtil::getI18NProvider); } /** diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/LocaleUtil.java b/flow-server/src/main/java/com/vaadin/flow/internal/LocaleUtil.java index 14c6d3f5458..22c645ca571 100644 --- a/flow-server/src/main/java/com/vaadin/flow/internal/LocaleUtil.java +++ b/flow-server/src/main/java/com/vaadin/flow/internal/LocaleUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2023 Vaadin Ltd. + * Copyright 2000-2022 Vaadin Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -19,15 +19,18 @@ import java.util.List; import java.util.Locale; import java.util.Optional; +import java.util.function.Supplier; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.i18n.I18NProvider; import com.vaadin.flow.server.VaadinRequest; +import com.vaadin.flow.server.VaadinService; /** * Utility class for locale handling. *

* For internal use only. May be renamed or removed in a future release. * - * @since 1.0 */ public final class LocaleUtil { @@ -84,4 +87,35 @@ public static Optional getLocaleMatchByLanguage( } return Optional.ofNullable(foundLocale); } + + /** + * Get the I18nProvider from the current VaadinService. + *

+ * + * @return the optional value of I18nProvider + */ + public static Optional getI18NProvider() { + return Optional.ofNullable( + VaadinService.getCurrent().getInstantiator().getI18NProvider()); + } + + /** + * Get the locale for the given UI. + *

+ * - If UI is not null, then it is used to get the locale, - if UI is null, + * then the I18NProvider providedLocales first match will be returned, - if + * I18NProvider is null, then default locale is returned. + * + * @param i18NProvider + * - supplier for the i18n provider + * @return the locale for the UI + */ + public static Locale getLocale( + Supplier> i18NProvider) { + return Optional.ofNullable(UI.getCurrent()).map(UI::getLocale) + .or(() -> i18NProvider.get() + .map(I18NProvider::getProvidedLocales) + .flatMap(locales -> locales.stream().findFirst())) + .orElseGet(Locale::getDefault); + } } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java index 1a7afdcaa22..86fb5043ecd 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java @@ -779,8 +779,8 @@ protected Lock lockSession(WrappedSession wrappedSession) { /** * Releases the lock for the given session for this service instance. - * Typically you want to call {@link VaadinSession#unlock()} instead of this - * method. + * Typically, you want to call {@link VaadinSession#unlock()} instead of + * this method. *

* Note: The method and its signature has been changed to get lock instance * as parameter in Vaadin X.X.0. If you have overriden this method, you need @@ -926,6 +926,7 @@ private VaadinSession createAndRegisterSession(VaadinRequest request) { private void setLocale(VaadinRequest request, VaadinSession session) { I18NProvider provider = getInstantiator().getI18NProvider(); List providedLocales = provider.getProvidedLocales(); + if (providedLocales.size() == 1) { session.setLocale(providedLocales.get(0)); } else { @@ -992,13 +993,7 @@ protected VaadinSession getExistingSession(VaadinRequest request, final WrappedSession session = getWrappedSession(request, allowSessionCreation); - VaadinSession vaadinSession = loadSession(session); - - if (vaadinSession == null) { - return null; - } - - return vaadinSession; + return loadSession(session); } /** diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandler.java index 4602acbf8bc..5fb30e6bab9 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandler.java @@ -15,13 +15,10 @@ */ package com.vaadin.flow.server.communication; -import static com.vaadin.flow.component.UI.SERVER_ROUTING; -import static com.vaadin.flow.shared.ApplicationConstants.CONTENT_TYPE_TEXT_HTML_UTF_8; -import static java.nio.charset.StandardCharsets.UTF_8; - import java.io.IOException; import java.io.Serializable; import java.io.UncheckedIOException; +import java.util.Locale; import java.util.Optional; import org.jsoup.Jsoup; @@ -37,6 +34,7 @@ import com.vaadin.flow.internal.BootstrapHandlerHelper; import com.vaadin.flow.internal.BrowserLiveReload; import com.vaadin.flow.internal.BrowserLiveReloadAccessor; +import com.vaadin.flow.internal.LocaleUtil; import com.vaadin.flow.internal.UsageStatisticsExporter; import com.vaadin.flow.internal.springcsrf.SpringCsrfTokenUtil; import com.vaadin.flow.server.AppShellRegistry; @@ -55,6 +53,10 @@ import elemental.json.JsonObject; import elemental.json.impl.JsonUtil; +import static com.vaadin.flow.component.UI.SERVER_ROUTING; +import static com.vaadin.flow.shared.ApplicationConstants.CONTENT_TYPE_TEXT_HTML_UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; + /** * This class is responsible for serving the index.html according * to the template provided in the frontend folder. The handler will calculate @@ -85,6 +87,12 @@ public boolean synchronizedHandleRequest(VaadinSession session, prependBaseHref(request, indexDocument); + Element htmlElement = indexDocument.getElementsByTag("html").get(0); + if (!htmlElement.hasAttr("lang")) { + Locale locale = LocaleUtil.getLocale(LocaleUtil::getI18NProvider); + htmlElement.attr("lang", locale.getLanguage()); + } + JsonObject initialJson = Json.createObject(); if (service.getBootstrapInitialPredicate() @@ -132,7 +140,7 @@ public boolean synchronizedHandleRequest(VaadinSession session, redirectToOldBrowserPageWhenNeeded(indexDocument); - // modify the page based on registered IndexHtmlRequestListener:s + // modify the page based on registered IndexHtmlRequestListener: service.modifyIndexHtmlResponse(indexHtmlResponse); if (!config.isProductionMode()) { diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/JavaScriptBootstrapHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/JavaScriptBootstrapHandler.java index 9b1b4681459..68ddc554274 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/JavaScriptBootstrapHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/JavaScriptBootstrapHandler.java @@ -54,13 +54,13 @@ /** * Processes a 'start' request type from the client to initialize server session - * and UI. It returns a JSON response with everything needed to bootstrapping - * flow views. + * and UI. It returns a JSON response with everything needed to bootstrap flow + * views. *

* The handler is for client driven projects where `index.html` does not contain - * bootstrap data. Bootstraping is the responsability of the `@vaadin/flow` + * bootstrap data. Bootstrapping is the responsibility of the `@vaadin/flow` * client that is able to ask the server side to create the vaadin session and - * do the boostrapping lazily. + * do the bootstrapping lazily. *

* For internal use only. May be renamed or removed in a future release. * diff --git a/flow-server/src/main/resources/com/vaadin/flow/server/frontend/index.html b/flow-server/src/main/resources/com/vaadin/flow/server/frontend/index.html index a5cdd4018a7..d36e593475c 100644 --- a/flow-server/src/main/resources/com/vaadin/flow/server/frontend/index.html +++ b/flow-server/src/main/resources/com/vaadin/flow/server/frontend/index.html @@ -3,7 +3,7 @@ This file is auto-generated by Vaadin. --> - + diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java index 98db00c3254..e99ffab906d 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java @@ -167,6 +167,17 @@ public void serveNotFoundIndexHtml_requestWithRootPath_failsWithIOException() Assert.assertEquals(expectedError, expectedException.getMessage()); } + @Test + public void serveIndexHtml_language_attribute_is_present() + throws IOException { + indexHtmlRequestHandler.synchronizedHandleRequest(session, + createVaadinRequest("/"), response); + String indexHtml = responseOutput + .toString(StandardCharsets.UTF_8.name()); + Assert.assertTrue("Response should have a language attribute", + indexHtml.contains(" - + diff --git a/flow-server/src/test/resources/frontend/index.html b/flow-server/src/test/resources/frontend/index.html index 0d45c39e173..8cde5d6785e 100644 --- a/flow-server/src/test/resources/frontend/index.html +++ b/flow-server/src/test/resources/frontend/index.html @@ -3,7 +3,7 @@ This file is auto-generated by Vaadin. --> - + diff --git a/flow-tests/test-ccdm-flow-navigation/frontend/index.html b/flow-tests/test-ccdm-flow-navigation/frontend/index.html index 870da89d663..8679d7796ff 100644 --- a/flow-tests/test-ccdm-flow-navigation/frontend/index.html +++ b/flow-tests/test-ccdm-flow-navigation/frontend/index.html @@ -1,5 +1,5 @@ - + diff --git a/flow-tests/test-ccdm/frontend/index.html b/flow-tests/test-ccdm/frontend/index.html index 96523a9c51f..06d1d057b54 100644 --- a/flow-tests/test-ccdm/frontend/index.html +++ b/flow-tests/test-ccdm/frontend/index.html @@ -1,5 +1,5 @@ - + diff --git a/flow-tests/test-express-build/test-theme-dev-bundle/frontend/index.html b/flow-tests/test-express-build/test-theme-dev-bundle/frontend/index.html index 5ca0ad0a1a5..10af5fff52a 100644 --- a/flow-tests/test-express-build/test-theme-dev-bundle/frontend/index.html +++ b/flow-tests/test-express-build/test-theme-dev-bundle/frontend/index.html @@ -3,7 +3,7 @@ This file is auto-generated by Vaadin. --> - + diff --git a/flow-tests/test-frontend/vite-basics/frontend/index.html b/flow-tests/test-frontend/vite-basics/frontend/index.html index 5c605f3d398..c4e0f277dc6 100644 --- a/flow-tests/test-frontend/vite-basics/frontend/index.html +++ b/flow-tests/test-frontend/vite-basics/frontend/index.html @@ -3,7 +3,7 @@ This file is auto-generated by Vaadin. --> - + diff --git a/flow-tests/test-frontend/vite-embedded-webcomponent-resync/frontend/index.html b/flow-tests/test-frontend/vite-embedded-webcomponent-resync/frontend/index.html index a5cdd4018a7..d36e593475c 100644 --- a/flow-tests/test-frontend/vite-embedded-webcomponent-resync/frontend/index.html +++ b/flow-tests/test-frontend/vite-embedded-webcomponent-resync/frontend/index.html @@ -3,7 +3,7 @@ This file is auto-generated by Vaadin. --> - + diff --git a/flow-tests/test-frontend/vite-embedded/frontend/index.html b/flow-tests/test-frontend/vite-embedded/frontend/index.html index a5cdd4018a7..d36e593475c 100644 --- a/flow-tests/test-frontend/vite-embedded/frontend/index.html +++ b/flow-tests/test-frontend/vite-embedded/frontend/index.html @@ -3,7 +3,7 @@ This file is auto-generated by Vaadin. --> - + diff --git a/flow-tests/test-frontend/vite-production/frontend/index.html b/flow-tests/test-frontend/vite-production/frontend/index.html index fc96e459156..22d40f41733 100644 --- a/flow-tests/test-frontend/vite-production/frontend/index.html +++ b/flow-tests/test-frontend/vite-production/frontend/index.html @@ -1,6 +1,6 @@ - + diff --git a/flow-tests/test-frontend/vite-pwa-custom-offline-path/src/main/webapp/offline.html b/flow-tests/test-frontend/vite-pwa-custom-offline-path/src/main/webapp/offline.html index 376ee42485b..8ac03cc92d9 100644 --- a/flow-tests/test-frontend/vite-pwa-custom-offline-path/src/main/webapp/offline.html +++ b/flow-tests/test-frontend/vite-pwa-custom-offline-path/src/main/webapp/offline.html @@ -1,5 +1,5 @@ - + - + diff --git a/flow-tests/test-frontend/vite-pwa/frontend/index.html b/flow-tests/test-frontend/vite-pwa/frontend/index.html index a5cdd4018a7..d36e593475c 100644 --- a/flow-tests/test-frontend/vite-pwa/frontend/index.html +++ b/flow-tests/test-frontend/vite-pwa/frontend/index.html @@ -3,7 +3,7 @@ This file is auto-generated by Vaadin. --> - + diff --git a/flow-tests/test-pwa/src/main/webapp/offline.html b/flow-tests/test-pwa/src/main/webapp/offline.html index e9fd4d16f1f..a6173559a4c 100644 --- a/flow-tests/test-pwa/src/main/webapp/offline.html +++ b/flow-tests/test-pwa/src/main/webapp/offline.html @@ -1,5 +1,5 @@ - + - + - +