Skip to content

Commit

Permalink
fix: i18n default translation from thread (#19136)
Browse files Browse the repository at this point in the history
Fixes getting translation bundle
when executing from thread.

Fixes #18977
  • Loading branch information
caalador committed Apr 11, 2024
1 parent 116900e commit a80ca77
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,13 @@ private I18NProvider getI18NProviderInstance() {
// If no i18n provider provided check if the default location has
// translation files (lang coded or just the default)
List<Locale> defaultTranslationLocales = I18NUtil
.getDefaultTranslationLocales();
.getDefaultTranslationLocales(getClassLoader());
if (!defaultTranslationLocales.isEmpty()
|| I18NUtil.containsDefaultTranslation()) {
|| I18NUtil.containsDefaultTranslation(getClassLoader())) {
// Some lang files were found in default location initialize
// default I18N provider.
return new DefaultI18NProvider(defaultTranslationLocales);
return new DefaultI18NProvider(defaultTranslationLocales,
getClassLoader());
}
return null;
}
Expand All @@ -128,6 +129,10 @@ private I18NProvider getI18NProviderInstance() {
return null;
}

protected ClassLoader getClassLoader() {
return getClass().getClassLoader();
}

/**
* Get the I18NProvider property from the session configurator or try to
* load it from application.properties property file.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2023 Vaadin Ltd.
* Copyright 2000-2024 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 Down Expand Up @@ -33,6 +33,7 @@
public class DefaultI18NProvider implements I18NProvider {

final List<Locale> providedLocales;
private final ClassLoader classLoader;

public static final String BUNDLE_FOLDER = "vaadin-i18n";
public static final String BUNDLE_FILENAME = "translations";
Expand All @@ -49,7 +50,23 @@ public class DefaultI18NProvider implements I18NProvider {
* locale.
*/
public DefaultI18NProvider(List<Locale> providedLocales) {
this(providedLocales, DefaultI18NProvider.class.getClassLoader());
}

/**
* Construct {@link DefaultI18NProvider} for a list of locales that we have
* translations for. Enables giving a specific classloader if needed.
*
* @param providedLocales
* List of locales. The first locale should be the default
* locale.
* @param classLoader
* ClassLoader to use for loading translation bundles.
*/
public DefaultI18NProvider(List<Locale> providedLocales,
ClassLoader classLoader) {
this.providedLocales = Collections.unmodifiableList(providedLocales);
this.classLoader = classLoader;
}

@Override
Expand Down Expand Up @@ -84,8 +101,7 @@ public String getTranslation(String key, Locale locale, Object... params) {

private ResourceBundle getBundle(Locale locale) {
try {
return ResourceBundle.getBundle(BUNDLE_PREFIX, locale,
I18NUtil.getClassLoader());
return ResourceBundle.getBundle(BUNDLE_PREFIX, locale, classLoader);
} catch (final MissingResourceException e) {
getLogger().warn("Missing resource bundle for " + BUNDLE_PREFIX
+ " and locale " + locale.getDisplayName(), e);
Expand Down
18 changes: 7 additions & 11 deletions flow-server/src/main/java/com/vaadin/flow/i18n/I18NUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,10 @@ public final class I18NUtil {
*
* @return {@code true} if default property file found
*/
public static boolean containsDefaultTranslation() {
URL resource = getClassLoader()
.getResource(DefaultI18NProvider.BUNDLE_FOLDER + "/"
+ DefaultI18NProvider.BUNDLE_FILENAME
+ PROPERTIES_SUFFIX);
public static boolean containsDefaultTranslation(ClassLoader classLoader) {
URL resource = classLoader.getResource(DefaultI18NProvider.BUNDLE_FOLDER
+ "/" + DefaultI18NProvider.BUNDLE_FILENAME
+ PROPERTIES_SUFFIX);
if (resource == null) {
return false;
}
Expand All @@ -73,10 +72,11 @@ public static boolean containsDefaultTranslation() {
*
* @return List of locales parsed from property files.
*/
public static List<Locale> getDefaultTranslationLocales() {
public static List<Locale> getDefaultTranslationLocales(
ClassLoader classLoader) {
List<Locale> locales = new ArrayList<>();

URL resource = getClassLoader()
URL resource = classLoader
.getResource(DefaultI18NProvider.BUNDLE_FOLDER);
if (resource == null) {
return locales;
Expand Down Expand Up @@ -157,10 +157,6 @@ protected static List<File> getTranslationFiles(URL resource) {
return files;
}

protected static ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}

protected static Logger getLogger() {
return LoggerFactory.getLogger(I18NUtil.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,39 +76,38 @@ public void translationFileOnClasspath_instantiateDefaultI18N()

createTranslationFiles(translations);

try (MockedStatic<I18NUtil> util = Mockito.mockStatic(I18NUtil.class,
Mockito.CALLS_REAL_METHODS)) {
util.when(() -> I18NUtil.getClassLoader())
.thenReturn(urlClassLoader);

VaadinService service = Mockito.mock(VaadinService.class);
mockLookup(service);

I18NProvider i18NProvider = new DefaultInstantiator(service)
.getI18NProvider();
Assert.assertNotNull(i18NProvider);
Assert.assertTrue(i18NProvider instanceof DefaultI18NProvider);

Assert.assertEquals("Suomi", i18NProvider.getTranslation("title",
new Locale("fi", "FI")));

Assert.assertEquals("deutsch",
i18NProvider.getTranslation("title", new Locale("de")));

Assert.assertEquals(
"non existing country should select language bundle",
"deutsch", i18NProvider.getTranslation("title",
new Locale("de", "AT")));

Assert.assertEquals("Korean", i18NProvider.getTranslation("title",
new Locale("ko", "KR")));

// Note!
// default translations.properties will be used if
// the locale AND system default locale is not found
Assert.assertEquals("Default lang", i18NProvider
.getTranslation("title", new Locale("en", "GB")));
}
VaadinService service = Mockito.mock(VaadinService.class);
mockLookup(service);

DefaultInstantiator defaultInstantiator = new DefaultInstantiator(
service) {
@Override
protected ClassLoader getClassLoader() {
return urlClassLoader;
}
};
I18NProvider i18NProvider = defaultInstantiator.getI18NProvider();
Assert.assertNotNull(i18NProvider);
Assert.assertTrue(i18NProvider instanceof DefaultI18NProvider);

Assert.assertEquals("Suomi",
i18NProvider.getTranslation("title", new Locale("fi", "FI")));

Assert.assertEquals("deutsch",
i18NProvider.getTranslation("title", new Locale("de")));

Assert.assertEquals(
"non existing country should select language bundle", "deutsch",
i18NProvider.getTranslation("title", new Locale("de", "AT")));

Assert.assertEquals("Korean",
i18NProvider.getTranslation("title", new Locale("ko", "KR")));

// Note!
// default translations.properties will be used if
// the locale AND system default locale is not found
Assert.assertEquals("Default lang",
i18NProvider.getTranslation("title", new Locale("en", "GB")));
}

@Test
Expand All @@ -119,34 +118,34 @@ public void onlyDefaultTranslation_instantiateDefaultI18N()
Files.writeString(file.toPath(), "title=Default lang",
StandardCharsets.UTF_8, StandardOpenOption.CREATE);

try (MockedStatic<I18NUtil> util = Mockito.mockStatic(I18NUtil.class,
Mockito.CALLS_REAL_METHODS)) {
util.when(() -> I18NUtil.getClassLoader())
.thenReturn(urlClassLoader);

VaadinService service = Mockito.mock(VaadinService.class);
mockLookup(service);

I18NProvider i18NProvider = new DefaultInstantiator(service)
.getI18NProvider();
Assert.assertNotNull(i18NProvider);
Assert.assertTrue(i18NProvider instanceof DefaultI18NProvider);

Assert.assertEquals("Default lang", i18NProvider
.getTranslation("title", new Locale("fi", "FI")));

Assert.assertEquals("Default lang",
i18NProvider.getTranslation("title", new Locale("de")));

Assert.assertEquals("Default lang", i18NProvider
.getTranslation("title", new Locale("ko", "KR")));

// Note!
// default translations.properties will be used if
// the locale AND system default locale is not found
Assert.assertEquals("Default lang", i18NProvider
.getTranslation("title", new Locale("en", "GB")));
}
VaadinService service = Mockito.mock(VaadinService.class);
mockLookup(service);

DefaultInstantiator defaultInstantiator = new DefaultInstantiator(
service) {
@Override
protected ClassLoader getClassLoader() {
return urlClassLoader;
}
};
I18NProvider i18NProvider = defaultInstantiator.getI18NProvider();
Assert.assertNotNull(i18NProvider);
Assert.assertTrue(i18NProvider instanceof DefaultI18NProvider);

Assert.assertEquals("Default lang",
i18NProvider.getTranslation("title", new Locale("fi", "FI")));

Assert.assertEquals("Default lang",
i18NProvider.getTranslation("title", new Locale("de")));

Assert.assertEquals("Default lang",
i18NProvider.getTranslation("title", new Locale("ko", "KR")));

// Note!
// default translations.properties will be used if
// the locale AND system default locale is not found
Assert.assertEquals("Default lang",
i18NProvider.getTranslation("title", new Locale("en", "GB")));
}

@Test
Expand All @@ -157,25 +156,25 @@ public void onlyLangTransalation_nonExistingLangReturnsKey()
Files.writeString(file.toPath(), "title=No Default",
StandardCharsets.UTF_8, StandardOpenOption.CREATE);

try (MockedStatic<I18NUtil> util = Mockito.mockStatic(I18NUtil.class,
Mockito.CALLS_REAL_METHODS)) {
util.when(() -> I18NUtil.getClassLoader())
.thenReturn(urlClassLoader);

VaadinService service = Mockito.mock(VaadinService.class);
mockLookup(service);

I18NProvider i18NProvider = new DefaultInstantiator(service)
.getI18NProvider();
Assert.assertNotNull(i18NProvider);
Assert.assertTrue(i18NProvider instanceof DefaultI18NProvider);

Assert.assertEquals("No Default",
i18NProvider.getTranslation("title", new Locale("ja")));

Assert.assertEquals("title", i18NProvider.getTranslation("title",
new Locale("en", "GB")));
}
VaadinService service = Mockito.mock(VaadinService.class);
mockLookup(service);

DefaultInstantiator defaultInstantiator = new DefaultInstantiator(
service) {
@Override
protected ClassLoader getClassLoader() {
return urlClassLoader;
}
};
I18NProvider i18NProvider = defaultInstantiator.getI18NProvider();
Assert.assertNotNull(i18NProvider);
Assert.assertTrue(i18NProvider instanceof DefaultI18NProvider);

Assert.assertEquals("No Default",
i18NProvider.getTranslation("title", new Locale("ja")));

Assert.assertEquals("title",
i18NProvider.getTranslation("title", new Locale("en", "GB")));
}

private static void createTranslationFiles(File translationsFolder)
Expand Down
Loading

0 comments on commit a80ca77

Please sign in to comment.