Skip to content

Commit

Permalink
fix/feature/optimization: use lang attribute in the html tag (#16410)…
Browse files Browse the repository at this point in the history
… (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)
  • Loading branch information
czp13 committed Apr 3, 2023
1 parent 0b053cd commit 8e4329a
Show file tree
Hide file tree
Showing 22 changed files with 94 additions and 58 deletions.
32 changes: 10 additions & 22 deletions flow-server/src/main/java/com/vaadin/flow/component/Component.java
Expand Up @@ -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;

Expand All @@ -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;

/**
Expand Down Expand Up @@ -636,10 +635,11 @@ protected boolean isTemplateMapped() {
* null)
*/
public String getTranslation(String key, Object... params) {
final Optional<I18NProvider> i18NProvider = getI18NProvider();
final Optional<I18NProvider> i18NProvider = LocaleUtil
.getI18NProvider();
return i18NProvider
.map(i18n -> i18n.getTranslation(key,
getLocale(() -> i18NProvider), params))
LocaleUtil.getLocale(() -> i18NProvider), params))
.orElseGet(() -> "!{" + key + "}!");
}

Expand All @@ -660,10 +660,11 @@ public String getTranslation(String key, Object... params) {
* null)
*/
public String getTranslation(Object key, Object... params) {
final Optional<I18NProvider> i18NProvider = getI18NProvider();
final Optional<I18NProvider> i18NProvider = LocaleUtil
.getI18NProvider();
return i18NProvider
.map(i18n -> i18n.getTranslation(key,
getLocale(() -> i18NProvider), params))
LocaleUtil.getLocale(() -> i18NProvider), params))
.orElseGet(() -> "!{" + key + "}!");
}

Expand All @@ -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 + "}!");
}
Expand All @@ -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 + "}!");
}
Expand Down Expand Up @@ -753,11 +754,6 @@ public String getTranslation(Locale locale, Object key, Object... params) {
return getTranslation(key, locale, params);
}

private Optional<I18NProvider> getI18NProvider() {
return Optional.ofNullable(
VaadinService.getCurrent().getInstantiator().getI18NProvider());
}

/**
* Gets the locale for this component.
* <p>
Expand All @@ -769,15 +765,7 @@ private Optional<I18NProvider> getI18NProvider() {
* @return the component locale
*/
protected Locale getLocale() {
return getLocale(() -> getI18NProvider());
}

private Locale getLocale(Supplier<Optional<I18NProvider>> 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);
}

/**
Expand Down
38 changes: 36 additions & 2 deletions 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
Expand All @@ -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.
* <p>
* For internal use only. May be renamed or removed in a future release.
*
* @since 1.0
*/
public final class LocaleUtil {

Expand Down Expand Up @@ -84,4 +87,35 @@ public static Optional<Locale> getLocaleMatchByLanguage(
}
return Optional.ofNullable(foundLocale);
}

/**
* Get the I18nProvider from the current VaadinService.
* <p>
*
* @return the optional value of I18nProvider
*/
public static Optional<I18NProvider> getI18NProvider() {
return Optional.ofNullable(
VaadinService.getCurrent().getInstantiator().getI18NProvider());
}

/**
* Get the locale for the given UI.
* <p>
* - 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<Optional<I18NProvider>> i18NProvider) {
return Optional.ofNullable(UI.getCurrent()).map(UI::getLocale)
.or(() -> i18NProvider.get()
.map(I18NProvider::getProvidedLocales)
.flatMap(locales -> locales.stream().findFirst()))
.orElseGet(Locale::getDefault);
}
}
Expand Up @@ -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.
* <p>
* 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
Expand Down Expand Up @@ -926,6 +926,7 @@ private VaadinSession createAndRegisterSession(VaadinRequest request) {
private void setLocale(VaadinRequest request, VaadinSession session) {
I18NProvider provider = getInstantiator().getI18NProvider();
List<Locale> providedLocales = provider.getProvidedLocales();

if (providedLocales.size() == 1) {
session.setLocale(providedLocales.get(0));
} else {
Expand Down Expand Up @@ -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);
}

/**
Expand Down
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 <code>index.html</code> according
* to the template provided in the frontend folder. The handler will calculate
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()) {
Expand Down
Expand Up @@ -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.
* <p>
* 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.
* <p>
* For internal use only. May be renamed or removed in a future release.
*
Expand Down
Expand Up @@ -3,7 +3,7 @@
This file is auto-generated by Vaadin.
-->

<html lang="en">
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
Expand Up @@ -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("<html lang"));
}

@Test
public void serveIndexHtml_requestWithRootPath_hasBaseHrefElement()
throws IOException {
Expand Down
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html>
<head>

<meta charset="UTF-8" />
Expand Down
2 changes: 1 addition & 1 deletion flow-server/src/test/resources/frontend/index.html
Expand Up @@ -3,7 +3,7 @@
This file is auto-generated by Vaadin.
-->

<html lang="en">
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
2 changes: 1 addition & 1 deletion flow-tests/test-ccdm-flow-navigation/frontend/index.html
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
2 changes: 1 addition & 1 deletion flow-tests/test-ccdm/frontend/index.html
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html>
<head>

<meta charset="UTF-8" />
Expand Down
Expand Up @@ -3,7 +3,7 @@
This file is auto-generated by Vaadin.
-->

<html lang="en">
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
2 changes: 1 addition & 1 deletion flow-tests/test-frontend/vite-basics/frontend/index.html
Expand Up @@ -3,7 +3,7 @@
This file is auto-generated by Vaadin.
-->

<html lang="en">
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
Expand Up @@ -3,7 +3,7 @@
This file is auto-generated by Vaadin.
-->

<html lang="en">
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
2 changes: 1 addition & 1 deletion flow-tests/test-frontend/vite-embedded/frontend/index.html
Expand Up @@ -3,7 +3,7 @@
This file is auto-generated by Vaadin.
-->

<html lang="en">
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
@@ -1,6 +1,6 @@
<!DOCTYPE html>

<html lang="en">
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
Expand Down
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
2 changes: 1 addition & 1 deletion flow-tests/test-frontend/vite-pwa/frontend/index.html
Expand Up @@ -3,7 +3,7 @@
This file is auto-generated by Vaadin.
-->

<html lang="en">
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
2 changes: 1 addition & 1 deletion flow-tests/test-pwa/src/main/webapp/offline.html
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
Expand Down

0 comments on commit 8e4329a

Please sign in to comment.