Skip to content

Commit 8e4329a

Browse files
authored
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)
1 parent 0b053cd commit 8e4329a

File tree

22 files changed

+94
-58
lines changed

22 files changed

+94
-58
lines changed

flow-server/src/main/java/com/vaadin/flow/component/Component.java

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.util.Collections;
2121
import java.util.Locale;
2222
import java.util.Optional;
23-
import java.util.function.Supplier;
2423
import java.util.stream.Stream;
2524
import java.util.stream.Stream.Builder;
2625

@@ -33,9 +32,9 @@
3332
import com.vaadin.flow.dom.ShadowRoot;
3433
import com.vaadin.flow.i18n.I18NProvider;
3534
import com.vaadin.flow.internal.AnnotationReader;
35+
import com.vaadin.flow.internal.LocaleUtil;
3636
import com.vaadin.flow.internal.nodefeature.ElementData;
3737
import com.vaadin.flow.server.Attributes;
38-
import com.vaadin.flow.server.VaadinService;
3938
import com.vaadin.flow.shared.Registration;
4039

4140
/**
@@ -636,10 +635,11 @@ protected boolean isTemplateMapped() {
636635
* null)
637636
*/
638637
public String getTranslation(String key, Object... params) {
639-
final Optional<I18NProvider> i18NProvider = getI18NProvider();
638+
final Optional<I18NProvider> i18NProvider = LocaleUtil
639+
.getI18NProvider();
640640
return i18NProvider
641641
.map(i18n -> i18n.getTranslation(key,
642-
getLocale(() -> i18NProvider), params))
642+
LocaleUtil.getLocale(() -> i18NProvider), params))
643643
.orElseGet(() -> "!{" + key + "}!");
644644
}
645645

@@ -660,10 +660,11 @@ public String getTranslation(String key, Object... params) {
660660
* null)
661661
*/
662662
public String getTranslation(Object key, Object... params) {
663-
final Optional<I18NProvider> i18NProvider = getI18NProvider();
663+
final Optional<I18NProvider> i18NProvider = LocaleUtil
664+
.getI18NProvider();
664665
return i18NProvider
665666
.map(i18n -> i18n.getTranslation(key,
666-
getLocale(() -> i18NProvider), params))
667+
LocaleUtil.getLocale(() -> i18NProvider), params))
667668
.orElseGet(() -> "!{" + key + "}!");
668669
}
669670

@@ -686,7 +687,7 @@ public String getTranslation(Object key, Object... params) {
686687
*/
687688
@Deprecated
688689
public String getTranslation(String key, Locale locale, Object... params) {
689-
return getI18NProvider()
690+
return LocaleUtil.getI18NProvider()
690691
.map(i18n -> i18n.getTranslation(key, locale, params))
691692
.orElseGet(() -> "!{" + key + "}!");
692693
}
@@ -710,7 +711,7 @@ public String getTranslation(String key, Locale locale, Object... params) {
710711
*/
711712
@Deprecated
712713
public String getTranslation(Object key, Locale locale, Object... params) {
713-
return getI18NProvider()
714+
return LocaleUtil.getI18NProvider()
714715
.map(i18n -> i18n.getTranslation(key, locale, params))
715716
.orElseGet(() -> "!{" + key + "}!");
716717
}
@@ -753,11 +754,6 @@ public String getTranslation(Locale locale, Object key, Object... params) {
753754
return getTranslation(key, locale, params);
754755
}
755756

756-
private Optional<I18NProvider> getI18NProvider() {
757-
return Optional.ofNullable(
758-
VaadinService.getCurrent().getInstantiator().getI18NProvider());
759-
}
760-
761757
/**
762758
* Gets the locale for this component.
763759
* <p>
@@ -769,15 +765,7 @@ private Optional<I18NProvider> getI18NProvider() {
769765
* @return the component locale
770766
*/
771767
protected Locale getLocale() {
772-
return getLocale(() -> getI18NProvider());
773-
}
774-
775-
private Locale getLocale(Supplier<Optional<I18NProvider>> i18NProvider) {
776-
return Optional.ofNullable(UI.getCurrent()).map(UI::getLocale)
777-
.or(() -> i18NProvider.get()
778-
.map(I18NProvider::getProvidedLocales)
779-
.flatMap(locales -> locales.stream().findFirst()))
780-
.orElseGet(Locale::getDefault);
768+
return LocaleUtil.getLocale(LocaleUtil::getI18NProvider);
781769
}
782770

783771
/**

flow-server/src/main/java/com/vaadin/flow/internal/LocaleUtil.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2000-2023 Vaadin Ltd.
2+
* Copyright 2000-2022 Vaadin Ltd.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
55
* use this file except in compliance with the License. You may obtain a copy of
@@ -19,15 +19,18 @@
1919
import java.util.List;
2020
import java.util.Locale;
2121
import java.util.Optional;
22+
import java.util.function.Supplier;
2223

24+
import com.vaadin.flow.component.UI;
25+
import com.vaadin.flow.i18n.I18NProvider;
2326
import com.vaadin.flow.server.VaadinRequest;
27+
import com.vaadin.flow.server.VaadinService;
2428

2529
/**
2630
* Utility class for locale handling.
2731
* <p>
2832
* For internal use only. May be renamed or removed in a future release.
2933
*
30-
* @since 1.0
3134
*/
3235
public final class LocaleUtil {
3336

@@ -84,4 +87,35 @@ public static Optional<Locale> getLocaleMatchByLanguage(
8487
}
8588
return Optional.ofNullable(foundLocale);
8689
}
90+
91+
/**
92+
* Get the I18nProvider from the current VaadinService.
93+
* <p>
94+
*
95+
* @return the optional value of I18nProvider
96+
*/
97+
public static Optional<I18NProvider> getI18NProvider() {
98+
return Optional.ofNullable(
99+
VaadinService.getCurrent().getInstantiator().getI18NProvider());
100+
}
101+
102+
/**
103+
* Get the locale for the given UI.
104+
* <p>
105+
* - If UI is not null, then it is used to get the locale, - if UI is null,
106+
* then the I18NProvider providedLocales first match will be returned, - if
107+
* I18NProvider is null, then default locale is returned.
108+
*
109+
* @param i18NProvider
110+
* - supplier for the i18n provider
111+
* @return the locale for the UI
112+
*/
113+
public static Locale getLocale(
114+
Supplier<Optional<I18NProvider>> i18NProvider) {
115+
return Optional.ofNullable(UI.getCurrent()).map(UI::getLocale)
116+
.or(() -> i18NProvider.get()
117+
.map(I18NProvider::getProvidedLocales)
118+
.flatMap(locales -> locales.stream().findFirst()))
119+
.orElseGet(Locale::getDefault);
120+
}
87121
}

flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -779,8 +779,8 @@ protected Lock lockSession(WrappedSession wrappedSession) {
779779

780780
/**
781781
* Releases the lock for the given session for this service instance.
782-
* Typically you want to call {@link VaadinSession#unlock()} instead of this
783-
* method.
782+
* Typically, you want to call {@link VaadinSession#unlock()} instead of
783+
* this method.
784784
* <p>
785785
* Note: The method and its signature has been changed to get lock instance
786786
* as parameter in Vaadin X.X.0. If you have overriden this method, you need
@@ -926,6 +926,7 @@ private VaadinSession createAndRegisterSession(VaadinRequest request) {
926926
private void setLocale(VaadinRequest request, VaadinSession session) {
927927
I18NProvider provider = getInstantiator().getI18NProvider();
928928
List<Locale> providedLocales = provider.getProvidedLocales();
929+
929930
if (providedLocales.size() == 1) {
930931
session.setLocale(providedLocales.get(0));
931932
} else {
@@ -992,13 +993,7 @@ protected VaadinSession getExistingSession(VaadinRequest request,
992993
final WrappedSession session = getWrappedSession(request,
993994
allowSessionCreation);
994995

995-
VaadinSession vaadinSession = loadSession(session);
996-
997-
if (vaadinSession == null) {
998-
return null;
999-
}
1000-
1001-
return vaadinSession;
996+
return loadSession(session);
1002997
}
1003998

1004999
/**

flow-server/src/main/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandler.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,10 @@
1515
*/
1616
package com.vaadin.flow.server.communication;
1717

18-
import static com.vaadin.flow.component.UI.SERVER_ROUTING;
19-
import static com.vaadin.flow.shared.ApplicationConstants.CONTENT_TYPE_TEXT_HTML_UTF_8;
20-
import static java.nio.charset.StandardCharsets.UTF_8;
21-
2218
import java.io.IOException;
2319
import java.io.Serializable;
2420
import java.io.UncheckedIOException;
21+
import java.util.Locale;
2522
import java.util.Optional;
2623

2724
import org.jsoup.Jsoup;
@@ -37,6 +34,7 @@
3734
import com.vaadin.flow.internal.BootstrapHandlerHelper;
3835
import com.vaadin.flow.internal.BrowserLiveReload;
3936
import com.vaadin.flow.internal.BrowserLiveReloadAccessor;
37+
import com.vaadin.flow.internal.LocaleUtil;
4038
import com.vaadin.flow.internal.UsageStatisticsExporter;
4139
import com.vaadin.flow.internal.springcsrf.SpringCsrfTokenUtil;
4240
import com.vaadin.flow.server.AppShellRegistry;
@@ -55,6 +53,10 @@
5553
import elemental.json.JsonObject;
5654
import elemental.json.impl.JsonUtil;
5755

56+
import static com.vaadin.flow.component.UI.SERVER_ROUTING;
57+
import static com.vaadin.flow.shared.ApplicationConstants.CONTENT_TYPE_TEXT_HTML_UTF_8;
58+
import static java.nio.charset.StandardCharsets.UTF_8;
59+
5860
/**
5961
* This class is responsible for serving the <code>index.html</code> according
6062
* to the template provided in the frontend folder. The handler will calculate
@@ -85,6 +87,12 @@ public boolean synchronizedHandleRequest(VaadinSession session,
8587

8688
prependBaseHref(request, indexDocument);
8789

90+
Element htmlElement = indexDocument.getElementsByTag("html").get(0);
91+
if (!htmlElement.hasAttr("lang")) {
92+
Locale locale = LocaleUtil.getLocale(LocaleUtil::getI18NProvider);
93+
htmlElement.attr("lang", locale.getLanguage());
94+
}
95+
8896
JsonObject initialJson = Json.createObject();
8997

9098
if (service.getBootstrapInitialPredicate()
@@ -132,7 +140,7 @@ public boolean synchronizedHandleRequest(VaadinSession session,
132140

133141
redirectToOldBrowserPageWhenNeeded(indexDocument);
134142

135-
// modify the page based on registered IndexHtmlRequestListener:s
143+
// modify the page based on registered IndexHtmlRequestListener:
136144
service.modifyIndexHtmlResponse(indexHtmlResponse);
137145

138146
if (!config.isProductionMode()) {

flow-server/src/main/java/com/vaadin/flow/server/communication/JavaScriptBootstrapHandler.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@
5454

5555
/**
5656
* Processes a 'start' request type from the client to initialize server session
57-
* and UI. It returns a JSON response with everything needed to bootstrapping
58-
* flow views.
57+
* and UI. It returns a JSON response with everything needed to bootstrap flow
58+
* views.
5959
* <p>
6060
* The handler is for client driven projects where `index.html` does not contain
61-
* bootstrap data. Bootstraping is the responsability of the `@vaadin/flow`
61+
* bootstrap data. Bootstrapping is the responsibility of the `@vaadin/flow`
6262
* client that is able to ask the server side to create the vaadin session and
63-
* do the boostrapping lazily.
63+
* do the bootstrapping lazily.
6464
* <p>
6565
* For internal use only. May be renamed or removed in a future release.
6666
*

flow-server/src/main/resources/com/vaadin/flow/server/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This file is auto-generated by Vaadin.
44
-->
55

6-
<html lang="en">
6+
<html>
77
<head>
88
<meta charset="UTF-8" />
99
<meta name="viewport" content="width=device-width, initial-scale=1" />

flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,17 @@ public void serveNotFoundIndexHtml_requestWithRootPath_failsWithIOException()
167167
Assert.assertEquals(expectedError, expectedException.getMessage());
168168
}
169169

170+
@Test
171+
public void serveIndexHtml_language_attribute_is_present()
172+
throws IOException {
173+
indexHtmlRequestHandler.synchronizedHandleRequest(session,
174+
createVaadinRequest("/"), response);
175+
String indexHtml = responseOutput
176+
.toString(StandardCharsets.UTF_8.name());
177+
Assert.assertTrue("Response should have a language attribute",
178+
indexHtml.contains("<html lang"));
179+
}
180+
170181
@Test
171182
public void serveIndexHtml_requestWithRootPath_hasBaseHrefElement()
172183
throws IOException {

flow-server/src/test/resources/META-INF/VAADIN/webapp/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!DOCTYPE html>
2-
<html lang="en">
2+
<html>
33
<head>
44

55
<meta charset="UTF-8" />

flow-server/src/test/resources/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This file is auto-generated by Vaadin.
44
-->
55

6-
<html lang="en">
6+
<html>
77
<head>
88
<meta charset="UTF-8" />
99
<meta name="viewport" content="width=device-width, initial-scale=1" />

flow-tests/test-ccdm-flow-navigation/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!DOCTYPE html>
2-
<html lang="en">
2+
<html>
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1" />

flow-tests/test-ccdm/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!DOCTYPE html>
2-
<html lang="en">
2+
<html>
33
<head>
44

55
<meta charset="UTF-8" />

flow-tests/test-express-build/test-theme-dev-bundle/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This file is auto-generated by Vaadin.
44
-->
55

6-
<html lang="en">
6+
<html>
77
<head>
88
<meta charset="UTF-8" />
99
<meta name="viewport" content="width=device-width, initial-scale=1" />

flow-tests/test-frontend/vite-basics/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This file is auto-generated by Vaadin.
44
-->
55

6-
<html lang="en">
6+
<html>
77
<head>
88
<meta charset="UTF-8" />
99
<meta name="viewport" content="width=device-width, initial-scale=1" />

flow-tests/test-frontend/vite-embedded-webcomponent-resync/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This file is auto-generated by Vaadin.
44
-->
55

6-
<html lang="en">
6+
<html>
77
<head>
88
<meta charset="UTF-8" />
99
<meta name="viewport" content="width=device-width, initial-scale=1" />

flow-tests/test-frontend/vite-embedded/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This file is auto-generated by Vaadin.
44
-->
55

6-
<html lang="en">
6+
<html>
77
<head>
88
<meta charset="UTF-8" />
99
<meta name="viewport" content="width=device-width, initial-scale=1" />

flow-tests/test-frontend/vite-production/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<!DOCTYPE html>
22

3-
<html lang="en">
3+
<html>
44
<head>
55
<meta charset="UTF-8" />
66
<meta name="viewport" content="width=device-width, initial-scale=1" />

flow-tests/test-frontend/vite-pwa-custom-offline-path/src/main/webapp/offline.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!doctype html>
2-
<html lang="en">
2+
<html>
33
<head>
44
<meta charset="UTF-8">
55
<meta name="viewport"

flow-tests/test-frontend/vite-pwa-production/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!DOCTYPE html>
2-
<html lang="en">
2+
<html>
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1" />

flow-tests/test-frontend/vite-pwa/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This file is auto-generated by Vaadin.
44
-->
55

6-
<html lang="en">
6+
<html>
77
<head>
88
<meta charset="UTF-8" />
99
<meta name="viewport" content="width=device-width, initial-scale=1" />

flow-tests/test-pwa/src/main/webapp/offline.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!doctype html>
2-
<html lang="en">
2+
<html>
33
<head>
44
<meta charset="UTF-8">
55
<meta name="viewport"

0 commit comments

Comments
 (0)